R4-React生命週期詳解及最新變動
這一章來介紹react元件的生命週期。之前介紹過純函式元件是沒有生命週期的,那到底生命週期是什麼?其實簡單來講就是元件的初始和消亡,就如同小草的生長一樣(配圖隨機,純屬護眼)從發芽到消亡。元件在這個過程中會經歷那些階段,又是如何標誌這些階段的。
本章的重點就是要搞明白的就是下面的重點以及思維導圖(前三點必須掌握):
1.元件生命週期有哪幾個階段?
2.每個階段又包含什麼?
3.每個階段周期函式的呼叫順序什麼?
*4.根據原始碼探究為什麼是這樣的順序?(感興趣的可以看看)
1.元件掛載階段
元件掛載是指元件建立例項時所必須經歷的一個過程,其中有三個函式是這一階段必須執行的。如思維導圖所示,分別是
componentWillMount():元件渲染之前執行的函式
render():渲染元件,該函式同樣屬於更新階段,負責渲染,無論是建立還是更新都需要重新渲染,就需要這個函式。
componentDidMount():元件渲染之後執行,且僅執行一次。想拿到元件例項只能在該函式中以及執行之後才可以。
2.元件更新階段
導致元件更新有兩種情況,父元件props發生變化,元件state值發生變化。元件更新階段稍微經歷的函式多一點,而且都帶了引數的,一定注意這些引數,寫元件的時候有大用。
componentWillReceiveProps(nextProps):該函式只在父元件創給子元件的屬性值,即子元件需要的props值發生變化時才會觸發執行。引數nextProps則是已經改變了的props值。
shouldComponentUpdate(nextProps,nextState): 控制是否要更新元件,他必須返回一個布林值,false不更新,true更新。不更新時則不再執行更新階段下面的函式。所以,引數nextProps和nextState都是已經改變的值,可根據他們判斷是否更新元件,由你自己掌握。
componentWillUpdate(nextProps,nextState): 更新元件渲染前執行,引數與上一個函式引數一致。
render(): 渲染更新的元件
componentDidUpdate(preProps,preState): 元件更新後即重新渲染後執行,注意引數,它是props和state變化前的值
3.解除安裝階段
解除安裝就比較簡單了,只有一個函式。componentWillUnmount.
生命週期是不是很簡單,就這幾個函式,記住執行順序,以及觸發條件,幹了什麼。你就已經基本掌握了生命週期了。生命週期的程式碼這裡就不貼了,去文章後面找到Github地址,下載原始碼,既可以看到例項,也可以跑出來看效果。
4.React生命週期的變動
很尷尬,之前做筆記的時候看的原始碼現在已經不合時宜了,react從16版本開始有了比較大的改動,剛去看一下官網最新的已經是16.5.2。原始碼部分非常多,這裡不再貼原始碼。我僅提煉最重要的進行說明。
第一點,生命週期中的多了兩個函式:static getDerivedStateFromProps,getSnapshotBeforeUpdate。
第二點,生命週期將在react17中徹底取消componentWillMount、componentWillReceiveProps和componentWillUpdate三個生命週期函式。17版本之前原生命週期依然保留,並添加了三個對應的帶有UNSAFE_字首的三個周期函式;且如果同時定義了getDerivedStateFromProps,則只會執行getDerivedStateFromProps。
mountClassInstance中的程式碼片段,元件掛載時,前兩個if正是用getDerivedStateFromProps替換掉了掛載時的componentWillMount
// 元件掛載時先判斷是否定義這個靜態函式,如果定義了,則不再執行componentWillMount方法
var getDerivedStateFromProps = workInProgress.type.getDerivedStateFromProps;
if (typeof getDerivedStateFromProps === 'function') {
applyDerivedStateFromProps(workInProgress, getDerivedStateFromProps, props);// 執行這個getDerivedStateFromProps方法
instance.state = workInProgress.memoizedState;
}
// ctor = workInProgress.type;如果定義了getDerivedStateFromProps則不再執行裡面的函式。
if (typeof ctor.getDerivedStateFromProps !== 'function' && typeof instance.getSnapshotBeforeUpdate !== 'function' && (typeof instance.UNSAFE_componentWillMount === 'function' || typeof instance.componentWillMount === 'function')) {
// 如果沒有定義getDeriveStateFromProps則執行此方法,此方法會判斷是否定義了componentWillMount方法,如果定義了則會執行
callComponentWillMount(workInProgress, instance);
updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
processUpdateQueue(workInProgress, updateQueue, props, instance, renderExpirationTime);
instance.state = workInProgress.memoizedState;
}
}
updateClassInstance中的程式碼片段,元件state和props變化引起元件更新如何替換掉了componentWillReceiveProps函式
var getDerivedStateFromProps = ctor.getDerivedStateFromProps;
// 是否定義了新的生命週期函式,如果定義了則在callComponentWillReceiveProps,該方法內會判斷是否定義了UNSAFE_字首的以及不加字首的componentWillReceiveProps方法,並執行。
var hasNewLifecycles = typeof getDerivedStateFromProps === 'function' || typeof instance.getSnapshotBeforeUpdate === 'function';
// 定義了新的生命週期函式則不再執行callComponentWillReceiveProps
if (!hasNewLifecycles && (typeof instance.UNSAFE_componentWillReceiveProps === 'function' || typeof instance.componentWillReceiveProps === 'function')) {
if (oldProps !== newProps || oldContext !== newContext) {
callComponentWillReceiveProps(workInProgress, instance, newProps, newContext);
}
}
同樣在updateClassInstance中,按照順序,componentWillReceiveProps執行之後是shouldComponentUpdate,並且傳入新的state和props。這裡也是一樣的,沒有變化。
// 判斷是否定義了新函式,定義了則去執行
if (typeof getDerivedStateFromProps === 'function') {
applyDerivedStateFromProps(workInProgress, getDerivedStateFromProps, newProps);
newState = workInProgress.memoizedState;
}
// 判斷是否更新元件,checkShouldComponentUpdate方法原始碼在下面
var shouldUpdate = checkHasForceUpdateAfterProcessing() || checkShouldComponentUpdate(workInProgress, oldProps, newProps, oldState, newState, newContext);
if (shouldUpdate) {// 根據是否要更新來決定是否執行下面的程式碼
// In order to support react-lifecycles-compat polyfilled components,
// Unsafe lifecycles should not be invoked for components using the new APIs.
// hasNewLifecycles就是上一個程式碼片段中的變數,元件中如果用新的生命週期函式則為true
if (!hasNewLifecycles && (typeof instance.UNSAFE_componentWillUpdate === 'function' || typeof instance.componentWillUpdate === 'function')) {
startPhaseTimer(workInProgress, 'componentWillUpdate');
if (typeof instance.componentWillUpdate === 'function') {
instance.componentWillUpdate(newProps, newState, newContext);// instance當前元件的例項
}
if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
instance.UNSAFE_componentWillUpdate(newProps, newState, newContext);
}
stopPhaseTimer();
}
// 這裡跟之前的版本很不一樣,之前是會先經過render,然後是在這裡立即判斷是否定義了componentDidUpdate函式,然後立即執行
if (typeof instance.componentDidUpdate === 'function') {
workInProgress.effectTag |= Update;// 現在則是通過位運算先標記,render之後在執行。
}
if (typeof instance.getSnapshotBeforeUpdate === 'function') {
workInProgress.effectTag |= Snapshot;
}
} else {....}
checkShouldComponentUpdate原始碼:
function checkShouldComponentUpdate(workInProgress, oldProps, newProps, oldState, newState, newContext) {
var instance = workInProgress.stateNode;
var ctor = workInProgress.type;
if (typeof instance.shouldComponentUpdate === 'function') {// 定義了則執行
startPhaseTimer(workInProgress, 'shouldComponentUpdate');
// 並把返回值付給shouldUpdate,依次作為返回值
var shouldUpdate = instance.shouldComponentUpdate(newProps, newState, newContext);
stopPhaseTimer();
{
!(shouldUpdate !== undefined) ? warning(false, '%s.shouldComponentUpdate(): Returned undefined instead of a ' + 'boolean value. Make sure to return true or false.', getComponentName(workInProgress) || 'Component') : void 0;
}
return shouldUpdate;
}
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
return !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState);
}
return true;// shouldComponentUpdate 方法預設返回true
}
render在哪裡?finishClassComponent程式碼片段。
if (didCaptureError && (!enableGetDerivedStateFromCatch || typeof ctor.getDerivedStateFromCatch !== 'function')) {
nextChildren = null;
if (enableProfilerTimer) {
stopBaseRenderTimerIfRunning();
}
} else {//沒有問題才會執行render函式
{
ReactDebugCurrentFiber.setCurrentPhase('render');
nextChildren = instance.render();
if (debugRenderPhaseSideEffects || debugRenderPhaseSideEffectsForStrictMode && workInProgress.mode & StrictMode) {
instance.render();// 這裡執行
}
ReactDebugCurrentFiber.setCurrentPhase(null);
}
}
finish之後會繼續判斷元件中是否還有其他元件,如果有繼續迴圈上面的過程,直到最後一個節點為null,然後才會一個一個的區執行ComponentDidMount或者ComponentDidUpdate方法,所以出現一個現象,子元件的Did字首的方法會先呼叫,就是這個原因。早些版本也是這樣是因為遞迴呼叫,現在這裡是一個無限迴圈,而且套了很多層。
還有更想深入瞭解React原始碼的童鞋,這裡有篇文章寫的非常不錯,他會在React整個架構上給你一個指導和思路,
本章的例項程式碼在study/lifeCycle資料夾下。工程原始碼地址,點選這裡