生命周期
初始化階段
constructor構(gòu)造函數(shù)getDefaultPropsprops默認(rèn)值getInitialStatestate默認(rèn)值掛載階段
componentWillMount組件初始化渲染前調(diào)用render組件渲染componentDidMount組件掛載到DOM后調(diào)用更新階段
componentWillReceiveProps組件將要接收新props前調(diào)用shouldComponentUpdate組件是否需要更新componentWillUpdate組件更新前調(diào)用render組件渲染componentDidUpdate組件更新后調(diào)用卸載階段
componentWillUnmount組件卸載前調(diào)用
生命周期
初始化階段
constructor構(gòu)造函數(shù)getDefaultPropsprops默認(rèn)值getInitialStatestate默認(rèn)值掛載階段
staticgetDerivedStateFromProps(props,state)rendercomponentDidMount
getDerivedStateFromProps:組件每次被 rerender的時候,包括在組件構(gòu)建之后(虛擬 dom之后,實(shí)際 dom掛載之前),每次獲取新的 props或 state之后;每次接收新的props之后都會返回一個對象作為新的 state,返回null則說明不需要更新 state;配合 componentDidUpdate,可以覆蓋 componentWillReceiveProps的所有用法
更新階段
staticgetDerivedStateFromProps(props,state)shouldComponentUpdaterendergetSnapshotBeforeUpdate(prevProps,prevState)componentDidUpdate
getSnapshotBeforeUpdate:觸發(fā)時間:update發(fā)生的時候,在render之后,在組件dom渲染之前;返回一個值,作為componentDidUpdate的第三個參數(shù);配合componentDidUpdate, 可以覆蓋componentWillUpdate`的所有用法
卸載階段
componentWillUnmount錯誤處理
componentDidCatch
React16新的生命周期棄用了 componentWillMount、componentWillReceivePorps,componentWillUpdate新增了 getDerivedStateFromProps、getSnapshotBeforeUpdate來代替棄用的三個鉤子函數(shù)。
React16并沒有刪除這三個鉤子函數(shù),但是不能和新增的鉤子函數(shù)混用,React17將會刪除這三個鉤子函數(shù),新增了對錯誤的處理(componentDidCatch`)
setState是同步的還是異步的?
- 生命周期和合成事件中
在 React的生命周期和合成事件中, React仍然處于他的更新機(jī)制中,這時無論調(diào)用多少次 setState,都會不會立即執(zhí)行更新,而是將要更新的·存入 _pendingStateQueue,將要更新的組件存入 dirtyComponent。
當(dāng)上一次更新機(jī)制執(zhí)行完畢,以生命周期為例,所有組件,即最頂層組件 didmount后會將批處理標(biāo)志設(shè)置為 false。這時將取出 dirtyComponent中的組件以及 _pendingStateQueue中的 state進(jìn)行更新。這樣就可以確保組件不會被重新渲染多次。
當(dāng)我們在執(zhí)行 setState后立即去獲取 state,這時是獲取不到更新后的 state的,因?yàn)樘幱?React的批處理機(jī)制中, state被暫存起來,待批處理機(jī)制完成之后,統(tǒng)一進(jìn)行更新。
所以。setState本身并不是異步的,而是 React的批處理機(jī)制給人一種異步的假象。
-
異步代碼和原生事件中
當(dāng)我們在異步代碼中調(diào)用setState時,根據(jù)JavaScript的異步機(jī)制,會將異步代碼先暫存,等所有同步代碼執(zhí)行完畢后在執(zhí)行,這時React的批處理機(jī)制已經(jīng)走完,處理標(biāo)志設(shè)被設(shè)置為false,這時再調(diào)用setState即可立即執(zhí)行更新,拿到更新后的結(jié)果。
在原生事件中調(diào)用 setState并不會出發(fā) React的批處理機(jī)制,所以立即能拿到最新結(jié)果。
- 最佳實(shí)踐
setState的第二個參數(shù)接收一個函數(shù),該函數(shù)會在 React的批處理機(jī)制完成之后調(diào)用,所以你想在調(diào)用 setState后立即獲取更新后的值,請?jiān)谠摶卣{(diào)函數(shù)中獲取。
為什么有時連續(xù)多次setState只有一次生效?
原因就是 React會批處理機(jī)制中存儲的多個 setState進(jìn)行合并,來看下 React源碼中的 _assign函數(shù),類似于 Object的 assign:
如果傳入的是對象,很明顯會被合并成一次,所以上面的代碼兩次打印的結(jié)果是相同的
- 最佳實(shí)踐
React會對多次連續(xù)的 setState進(jìn)行合并,如果你想立即使用上次 setState后的結(jié)果進(jìn)行下一次 setState,可以讓 setState 接收一個函數(shù)而不是一個對象。這個函數(shù)用上一個 state 作為第一個參數(shù),將此次更新被應(yīng)用時的 props 做為第二個參數(shù)。
React如何實(shí)現(xiàn)自己的事件機(jī)制?
React事件并沒有綁定在真實(shí)的 Dom節(jié)點(diǎn)上,而是通過事件代理,在最外層的 document上對事件進(jìn)行統(tǒng)一分發(fā)。
組件掛載、更新時:
通過
lastProps、nextProps判斷是否新增、刪除事件分別調(diào)用事件注冊、卸載方法。調(diào)用
EventPluginHub的enqueuePutListener進(jìn)行事件存儲獲取
document對象。根據(jù)事件名稱(如
onClick、onCaptureClick)判斷是進(jìn)行冒泡還是捕獲。判斷是否存在
addEventListener方法,否則使用attachEvent(兼容IE)。給
document注冊原生事件回調(diào)為dispatchEvent(統(tǒng)一的事件分發(fā)機(jī)制)。
事件初始化:
EventPluginHub負(fù)責(zé)管理React合成事件的callback,它將callback存儲在listenerBank中,另外還存儲了負(fù)責(zé)合成事件的Plugin。獲取綁定事件的元素的唯一標(biāo)識
key。將
callback根據(jù)事件類型,元素的唯一標(biāo)識key存儲在listenerBank中。listenerBank的結(jié)構(gòu)是:listenerBank[registrationName][key]。
觸發(fā)事件時:
觸發(fā)
document注冊原生事件的回調(diào)dispatchEvent獲取到觸發(fā)這個事件最深一級的元素
遍歷這個元素的所有父元素,依次對每一級元素進(jìn)行處理。
構(gòu)造合成事件。
將每一級的合成事件存儲在
eventQueue事件隊(duì)列中。遍歷
eventQueue。通過
isPropagationStopped判斷當(dāng)前事件是否執(zhí)行了阻止冒泡方法。如果阻止了冒泡,停止遍歷,否則通過
executeDispatch執(zhí)行合成事件。釋放處理完成的事件。
React在自己的合成事件中重寫了 stopPropagation方法,將 isPropagationStopped設(shè)置為 true,然后在遍歷每一級事件的過程中根據(jù)此遍歷判斷是否繼續(xù)執(zhí)行。這就是 React自己實(shí)現(xiàn)的冒泡機(jī)制。
為何React事件要自己綁定this?
在上面提到的事件處理流程中, React在 document上進(jìn)行統(tǒng)一的事件分發(fā), dispatchEvent通過循環(huán)調(diào)用所有層級的事件來模擬事件冒泡和捕獲。
在 React源碼中,當(dāng)具體到某一事件處理函數(shù)將要調(diào)用時,將調(diào)用 invokeGuardedCallback方法。
function invokeGuardedCallback(name, func, a) {
try {
func(a);
} catch (x) {
if (caughtError === null) {
caughtError = x;
}
}}
可見,事件處理函數(shù)是直接調(diào)用的,并沒有指定調(diào)用的組件,所以不進(jìn)行手動綁定的情況下直接獲取到的 this是不準(zhǔn)確的,所以我們需要手動將當(dāng)前組件綁定到 this上。
原生事件和React事件的區(qū)別?
React事件使用駝峰命名,而不是全部小寫。通過
JSX, 你傳遞一個函數(shù)作為事件處理程序,而不是一個字符串。在
React中你不能通過返回false來阻止默認(rèn)行為。必須明確調(diào)用preventDefault。
React的合成事件是什么?
React 根據(jù) W3C 規(guī)范定義了每個事件處理函數(shù)的參數(shù),即合成事件。
事件處理程序?qū)鬟f SyntheticEvent 的實(shí)例,這是一個跨瀏覽器原生事件包裝器。它具有與瀏覽器原生事件相同的接口,包括 stopPropagation() 和 preventDefault(),在所有瀏覽器中他們工作方式都相同。
React合成的 SyntheticEvent采用了事件池,這樣做可以大大節(jié)省內(nèi)存,而不會頻繁的創(chuàng)建和銷毀事件對象。
另外,不管在什么瀏覽器環(huán)境下,瀏覽器會將該事件類型統(tǒng)一創(chuàng)建為合成事件,從而達(dá)到了瀏覽器兼容的目的。
React和原生事件的執(zhí)行順序是什么?可以混用嗎?
React的所有事件都通過 document進(jìn)行統(tǒng)一分發(fā)。當(dāng)真實(shí) Dom觸發(fā)事件后冒泡到 document后才會對 React事件進(jìn)行處理。
所以原生的事件會先執(zhí)行,然后執(zhí)行 React合成事件,最后執(zhí)行真正在 document上掛載的事件
React事件和原生事件最好不要混用。原生事件中如果執(zhí)行了 stopPropagation方法,則會導(dǎo)致其他 React事件失效。因?yàn)樗性氐氖录o法冒泡到 document上,導(dǎo)致所有的 React事件都將無法被觸發(fā)。。
虛擬Dom是什么?
在原生的 JavaScript程序中,我們直接對 DOM進(jìn)行創(chuàng)建和更改,而 DOM元素通過我們監(jiān)聽的事件和我們的應(yīng)用程序進(jìn)行通訊。
而 React會先將你的代碼轉(zhuǎn)換成一個 JavaScript對象,然后這個 JavaScript對象再轉(zhuǎn)換成真實(shí) DOM。這個 JavaScript對象就是所謂的虛擬 DOM。
當(dāng)我們需要創(chuàng)建或更新元素時, React首先會讓這個 VitrualDom對象進(jìn)行創(chuàng)建和更改,然后再將 VitrualDom對象渲染成真實(shí)DOM。
當(dāng)我們需要對 DOM進(jìn)行事件監(jiān)聽時,首先對 VitrualDom進(jìn)行事件監(jiān)聽, VitrualDom會代理原生的 DOM事件從而做出響應(yīng)。
虛擬Dom比普通Dom更快嗎?
很多文章說 VitrualDom可以提升性能,這一說法實(shí)際上是很片面的。
直接操作 DOM是非常耗費(fèi)性能的,這一點(diǎn)毋庸置疑。但是 React使用 VitrualDom也是無法避免操作 DOM的。
如果是首次渲染, VitrualDom不具有任何優(yōu)勢,甚至它要進(jìn)行更多的計(jì)算,消耗更多的內(nèi)存。
VitrualDom的優(yōu)勢在于 React的 Diff算法和批處理策略, React在頁面更新之前,提前計(jì)算好了如何進(jìn)行更新和渲染 DOM。實(shí)際上,這個計(jì)算過程我們在直接操作 DOM時,也是可以自己判斷和實(shí)現(xiàn)的,但是一定會耗費(fèi)非常多的精力和時間,而且往往我們自己做的是不如 React好的。所以,在這個過程中 React幫助我們"提升了性能"。
所以,我更傾向于說, VitrualDom幫助我們提高了開發(fā)效率,在重復(fù)渲染時它幫助我們計(jì)算如何更高效的更新,而不是它比 DOM操作更快。
虛擬Dom中的$$typeof屬性的作用是什么?
ReactElement中有一個 $$typeof屬性,它被賦值為 REACT_ELEMENT_TYPE:
var REACT_ELEMENT_TYPE =`
(typeof Symbol === 'function' && Symbol.for && Symbol.for('react.element')) ||
0xeac7;
可見, $$typeof是一個 Symbol類型的變量,這個變量可以防止 XSS。
如果你的服務(wù)器有一個漏洞,允許用戶存儲任意 JSON對象, 而客戶端代碼需要一個字符串,這可能會成為一個問題:
// JSON
let expectedTextButGotJSON = {
type: 'div',
props: {
dangerouslySetInnerHTML: {
__html: '/* put your exploit here */'
},
},
};
let message = { text: expectedTextButGotJSON };
<p>
{message.text}
</p>
JSON中不能存儲 Symbol類型的變量。
ReactElement.isValidElement函數(shù)用來判斷一個 React組件是否是有效的??梢?React渲染時會把沒有 $$typeof標(biāo)識,以及規(guī)則校驗(yàn)不通過的組件過濾掉。
當(dāng)你的環(huán)境不支持 Symbol時, $$typeof被賦值為 0xeac7,至于為什么, React開發(fā)者給出了答案:
0xeac7看起來有點(diǎn)像React。
React組件的渲染流程是什么?
使用
React.createElement或JSX編寫React組件,實(shí)際上所有的JSX代碼最后都會轉(zhuǎn)換成React.createElement(...),Babel幫助我們完成了這個轉(zhuǎn)換的過程。createElement函數(shù)對key和ref等特殊的props進(jìn)行處理,并獲取defaultProps對默認(rèn)props進(jìn)行賦值,并且對傳入的孩子節(jié)點(diǎn)進(jìn)行處理,最終構(gòu)造成一個ReactElement對象(所謂的虛擬DOM)。ReactDOM.render將生成好的虛擬DOM渲染到指定容器上,其中采用了批處理、事務(wù)等機(jī)制并且對特定瀏覽器進(jìn)行了性能優(yōu)化,最終轉(zhuǎn)換為真實(shí)DOM。
為什么代碼中一定要引入React?
JSX只是為 React.createElement(component,props,...children)方法提供的語法糖。
所有的 JSX代碼最后都會轉(zhuǎn)換成 React.createElement(...), Babel幫助我們完成了這個轉(zhuǎn)換的過程。
所以使用了 JSX的代碼都必須引入 React。
為什么React組件首字母必須大寫?
babel在編譯時會判斷 JSX中組件的首字母,當(dāng)首字母為小寫時,其被認(rèn)定為原生 DOM標(biāo)簽, createElement的第一個變量被編譯為字符串;當(dāng)首字母為大寫時,其被認(rèn)定為自定義組件, createElement的第一個變量被編譯為對象;
React在渲染真實(shí)Dom時做了哪些性能優(yōu)化?
在 IE(8-11)和 Edge瀏覽器中,一個一個插入無子孫的節(jié)點(diǎn),效率要遠(yuǎn)高于插入一整個序列化完整的節(jié)點(diǎn)樹。
React通過 lazyTree,在 IE(8-11)和 Edge中進(jìn)行單個節(jié)點(diǎn)依次渲染節(jié)點(diǎn),而在其他瀏覽器中則首先將整個大的 DOM結(jié)構(gòu)構(gòu)建好,然后再整體插入容器。
并且,在單獨(dú)渲染節(jié)點(diǎn)時, React還考慮了 fragment等特殊節(jié)點(diǎn),這些節(jié)點(diǎn)則不會一個一個插入渲染。
什么是高階組件?如何實(shí)現(xiàn)?
高階組件可以看作 React對裝飾模式的一種實(shí)現(xiàn),高階組件就是一個函數(shù),且該函數(shù)接受一個組件作為參數(shù),并返回一個新的組件。
高階組件( HOC)是 React中的高級技術(shù),用來重用組件邏輯。但高階組件本身并不是 ReactAPI。它只是一種模式,這種模式是由 React自身的組合性質(zhì)必然產(chǎn)生的。
function visible(WrappedComponent) {
return class extends Component {
render() {
const { visible, ...props } = this.props;
if (visible === false) return null;
return <WrappedComponent {...props} />;
}
}
}
</pre>
上面的代碼就是一個 HOC的簡單應(yīng)用,函數(shù)接收一個組件作為參數(shù),并返回一個新組件,新組建可以接收一個 visible props,根據(jù) visible的值來判斷是否渲染Visible。
我們可以通過以下兩種方式實(shí)現(xiàn)高階組件:
屬性代理
函數(shù)返回一個我們自己定義的組件,然后在 render中返回要包裹的組件,這樣我們就可以代理所有傳入的 props,并且決定如何渲染,實(shí)際上 ,這種方式生成的高階組件就是原組件的父組件,上面的函數(shù) visible就是一個 HOC屬性代理的實(shí)現(xiàn)方式。
function proxyHOC(WrappedComponent) {
return class extends Component {
render() {
return <WrappedComponent {...this.props} />;
}
}
}
對比原生組件增強(qiáng)的項(xiàng):
可操作所有傳入的
props可操作組件的生命周期
可操作組件的
static方法獲取
refs
反向繼承
返回一個組件,繼承原組件,在 render中調(diào)用原組件的 render。由于繼承了原組件,能通過this訪問到原組件的 生命周期、props、state、render等,相比屬性代理它能操作更多的屬性。
function inheritHOC(WrappedComponent) {
return class extends WrappedComponent {
render() {
return super.render();
}
}
}
對比原生組件增強(qiáng)的項(xiàng):
可操作所有傳入的
props可操作組件的生命周期
可操作組件的
static方法獲取
refs可操作
state可以渲染劫持
HOC在業(yè)務(wù)場景中有哪些實(shí)際應(yīng)用場景?
HOC可以實(shí)現(xiàn)的功能:
組合渲染
條件渲染
操作
props獲取
refs狀態(tài)管理
操作
state渲染劫持
HOC在業(yè)務(wù)中的實(shí)際應(yīng)用場景:
日志打點(diǎn)
權(quán)限控制
雙向綁定
表單校驗(yàn)
高階組件(HOC)和Mixin的異同點(diǎn)是什么?
Mixin和 HOC都可以用來解決 React的代碼復(fù)用問題。
Mixin可能會相互依賴,相互耦合,不利于代碼維護(hù)不同的
Mixin中的方法可能會相互沖突Mixin非常多時,組件是可以感知到的,甚至還要為其做相關(guān)處理,這樣會給代碼造成滾雪球式的復(fù)雜性
而 HOC的出現(xiàn)可以解決這些問題:
高階組件就是一個沒有副作用的純函數(shù),各個高階組件不會互相依賴耦合
高階組件也有可能造成沖突,但我們可以在遵守約定的情況下避免這些行為
高階組件并不關(guān)心數(shù)據(jù)使用的方式和原因,而被包裹的組件也不關(guān)心數(shù)據(jù)來自何處。高階組件的增加不會為原組件增加負(fù)擔(dān)
Hook有哪些優(yōu)勢?
- 減少狀態(tài)邏輯復(fù)用的風(fēng)險
Hook和 Mixin在用法上有一定的相似之處,但是 Mixin引入的邏輯和狀態(tài)是可以相互覆蓋的,而多個 Hook之間互不影響,這讓我們不需要在把一部分精力放在防止避免邏輯復(fù)用的沖突上。在不遵守約定的情況下使用 HOC也有可能帶來一定沖突,比如 props覆蓋等等,使用 Hook則可以避免這些問題。
- 避免地獄式嵌套
大量使用 HOC的情況下讓我們的代碼變得嵌套層級非常深,使用 HOC,我們可以實(shí)現(xiàn)扁平式的狀態(tài)邏輯復(fù)用,而避免了大量的組件嵌套。
- 讓組件更容易理解
在使用 class組件構(gòu)建我們的程序時,他們各自擁有自己的狀態(tài),業(yè)務(wù)邏輯的復(fù)雜使這些組件變得越來越龐大,各個生命周期中會調(diào)用越來越多的邏輯,越來越難以維護(hù)。使用 Hook,可以讓你更大限度的將公用邏輯抽離,將一個組件分割成更小的函數(shù),而不是強(qiáng)制基于生命周期方法進(jìn)行分割。
- 使用函數(shù)代替class
相比函數(shù),編寫一個 class可能需要掌握更多的知識,需要注意的點(diǎn)也越多,比如 this指向、綁定事件等等。另外,計(jì)算機(jī)理解一個 class比理解一個函數(shù)更快。Hooks讓你可以在 classes之外使用更多 React的新特性。