React學習過程當中,對於組件最重要的(也多是之一)的莫過因而組件的生命週期了,React至關於使用狀態來映射到界面輸出,經過狀態的改變從而改變界面效果。在狀態的改變過程當中,必需要經歷組件的生命週期。javascript
React會經歷三個階段:mount、update、unmount,每一個階段對應兩個生命週期(ummount除外):Will(對應進入)與Did(對應結束),於是存在五個對應的方法,而且在update階段存在兩種特殊的方法:shouldComponentUpdate
與componentWillReceiveProps
,這
些函數基本構成了React的生命週期。以下圖所示:java
上圖中的getDefaultProps
和getInitialState
分別對應ES6中的static defaultProps = {}
與構造函數construct
中的this.state ={}
賦值。下面咱們按照上圖的過程依次介紹:(介紹主要以React.createClass
爲例,基本等同於extends React.Component
)react
//本文代碼基於15.0,只刪選其中有用的部分,註釋來源於《深刻React技術棧》 var React = { //... createClass: ReactClass.createClass //... }; var ReactClass = { // 建立自定義組件 createClass: function(spec) { var Constructor = function(props, context, updater) { // 自動綁定 if (this.__reactAutoBindPairs.length) { bindAutoBindMethods(this); } this.props = props; this.context = context; this.refs = emptyObject; this.updater = updater || ReactNoopUpdateQueue; this.state = null; // ReactClass 沒有構造函數,經過 getInitialState 和 componentWillMount 來代替 var initialState = this.getInitialState ? this.getInitialState() : null; this.state = initialState; }; // 原型繼承父類 Constructor.prototype = new ReactClassComponent(); Constructor.prototype.constructor = Constructor; Constructor.prototype.__reactAutoBindPairs = []; // 合併 mixin injectedMixins.forEach( mixSpecIntoComponent.bind(null, Constructor) ); mixSpecIntoComponent(Constructor, spec); // 全部 mixin 合併後初始化 defaultProps(在整個生命週期中,getDefaultProps 只執行一次) if (Constructor.getDefaultProps) { Constructor.defaultProps = Constructor.getDefaultProps(); } // 減小查找並設置原型的時間 for (var methodName in ReactClassInterface) { if (!Constructor.prototype[methodName]) { Constructor.prototype[methodName] = null; } } return Constructor; }, };
總結一下上面的代碼React.createClass
返回函數Constructor(props, context, updater)
用來生成組件的實例,而React.createClass
執行的時候會執行包括:合併mixin,獲取默認屬性defaultProps
將其賦值到Constructor
的原型中,而且也將傳入React.createClass
中的方法賦值到Constructor
的原型中,以縮短再次查找方法的時間。
在這個函數中,咱們關心的部分其實主要集中在:瀏覽器
if (Constructor.getDefaultProps) { Constructor.defaultProps = Constructor.getDefaultProps(); }
咱們發如今調用React.createClass
,已經執行了getDefaultProps()
,並將其賦值於Constructor
的原型中,因此咱們對照聲明週期圖能夠獲得:app
React中的getDefaultProps()僅會在整個生命週期中只執行一次,而且初始化的實例都會共享該
defaultProps
less
ReactCompositeComponent
中的mountComponent
、updateComponent
、unmountComponent
分別對應於React中mount、update、unmount階段的處理,首先大體看一下mount階段的簡要代碼:函數
// 當組件掛載時,會分配一個遞增編號,表示執行 ReactUpdates 時更新組件的順序 var nextMountID = 1; var ReactCompositeComponent = { /** * 組件初始化,渲染、註冊事件 * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction * @param {?object} hostParent * @param {?object} hostContainerInfo * @param {?object} context * @return {?string} 返回的markup會被插入DOM中. * @final * @internal */ mountComponent: function (transaction, nativeParent, nativeContainerInfo, context) { // 當前元素對應的上下文 this._context = context; this._mountOrder = nextMountID++; this._nativeParent = nativeParent; this._nativeContainerInfo = nativeContainerInfo; var publicProps = this._processProps(this._currentElement.props); var publicContext = this._processContext(context); var Component = this._currentElement.type; // 初始化公共類 var inst = this._constructComponent(publicProps, publicContext); var renderedElement; // 用於判斷組件是否爲 stateless,無狀態組件沒有狀態更新隊列,它只專一於渲染 if (!shouldConstruct(Component) && (inst == null || inst.render == null)) { renderedElement = inst; warnIfInvalidElement(Component, renderedElement); inst = new StatelessComponent(Component); } // 這些初始化參數本應該在構造函數中設置,在此設置是爲了便於進行簡單的類抽象 inst.props = publicProps; inst.context = publicContext; inst.refs = emptyObject; inst.updater = ReactUpdateQueue; this._instance = inst; // 將實例存儲爲一個引用 ReactInstanceMap.set(inst, this); // 初始化 state var initialState = inst.state; if (initialState === undefined) { inst.state = initialState = null; } // 初始化更新隊列 this._pendingStateQueue = null; this._pendingReplaceState = false; this._pendingForceUpdate = false; var markup; // 若是掛載時出現錯誤 if (inst.unstable_handleError) { markup = this.performInitialMountWithErrorHandling(renderedElement, nativeParent, nativeContainerInfo, transaction, context); } else { // 執行初始化掛載 markup = this.performInitialMount(renderedElement, nativeParent, nativeContainerInfo, transaction, context); } // 若是存在 componentDidMount,則調用 if (inst.componentDidMount) { transaction.getReactMountReady().enqueue(inst.componentDidMount, inst); } return markup; }, performInitialMountWithErrorHandling: function (renderedElement, nativeParent, nativeContainerInfo, transaction, context) { var markup; var checkpoint = transaction.checkpoint(); try { // 捕捉錯誤,若是沒有錯誤,則初始化掛載 markup = this.performInitialMount(renderedElement, nativeParent, nativeContainerInfo, transaction, context); } catch (e) { transaction.rollback(checkpoint); this._instance.unstable_handleError(e); if (this._pendingStateQueue) { this._instance.state = this._processPendingState(this._instance.props, this._instance.context); } checkpoint = transaction.checkpoint(); // 若是捕捉到錯誤,則執行 unmountComponent 後,再初始化掛載 this._renderedComponent.unmountComponent(true); transaction.rollback(checkpoint); markup = this.performInitialMount(renderedElement, nativeParent, nativeContainerInfo, transaction, context); } return markup; }, performInitialMount: function (renderedElement, nativeParent, nativeContainerInfo, transaction, context) { var inst = this._instance; // 若是存在 componentWillMount,則調用 if (inst.componentWillMount) { inst.componentWillMount(); // componentWillMount 調用 setState 時,不會觸發 re-render 而是自動提早合併 if (this._pendingStateQueue) { inst.state = this._processPendingState(inst.props, inst.context); } } // 若是不是無狀態組件,便可開始渲染 if (renderedElement === undefined) { renderedElement = this._renderValidatedComponent(); } this._renderedNodeType = ReactNodeTypes.getType(renderedElement); // 獲得 _currentElement 對應的 component 類實例 this._renderedComponent = this._instantiateReactComponent( renderedElement ); // render 遞歸渲染 var markup = ReactReconciler.mountComponent(this._renderedComponent, transaction, nativeParent, nativeContainerInfo, this._processChildContext(context)); return markup; } }
咱們如今只關心生命週期相關的代碼,初始化及其餘的代碼暫時不考慮,咱們發現初始化state
以後會進入渲染的步驟,根據是否存在錯誤,選擇性執行performInitialMountWithErrorHandling
與performInitialMount
,咱們僅考慮正常狀況下的performInitialMount
。oop
// 若是存在 componentWillMount,則調用 if (inst.componentWillMount) { inst.componentWillMount(); // componentWillMount 調用 setState 時,不會觸發 re-render 而是自動提早合併 if (this._pendingStateQueue) { inst.state = this._processPendingState(inst.props, inst.context); } }
若是存在componentWillMount
則執行,若是在componentWillMount
執行了setState
方法,在componentWillMount
並不會獲得已經更新的state
,由於咱們發現的state
的合併過程是在componentWillMount
結束後才執行的。而後在performInitialMount
(爲例)會進行遞歸渲染,
而後在遞歸執行結束後,返回markup
(返回的markup會被插入DOM中)。而後,若是存在 componentDidMount。而且因爲渲染的過程都是遞歸的,咱們能夠綜合獲得渲染階段的生命週期(包括子節點)以下:學習
首先仍是看一下簡要的更新階段的代碼:this
var ReactCompositeComponent = { /** * 更新已經渲染的組件。componentWillReceiveProps和shouldComponentUpdate方法將會被調用 * 而後,(更新的過程沒有被省略),其他的更新階段的生命週期都會被調用,對應的DOM會被更新。 * @param {ReactReconcileTransaction} transaction * @param {ReactElement} prevParentElement * @param {ReactElement} nextParentElement * @internal * @overridable */ updateComponent: function (transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext) { var inst = this._instance; var willReceive = false; var nextContext; var nextProps; // Determine if the context has changed or not if (this._context === nextUnmaskedContext) { nextContext = inst.context; } else { nextContext = this._processContext(nextUnmaskedContext); willReceive = true; } var prevProps = prevParentElement.props; var nextProps = nextParentElement.props; // Not a simple state update but a props update if (prevParentElement !== nextParentElement) { willReceive = true; } // 若是存在 componentWillReceiveProps,則調用 if (willReceive && inst.componentWillReceiveProps) { inst.componentWillReceiveProps(nextProps, nextContext); } // 將新的 state 合併到更新隊列中,此時 nextState 爲最新的 state var nextState = this._processPendingState(nextProps, nextContext); // 根據更新隊列和 shouldComponentUpdate 的狀態來判斷是否須要更新組件 var shouldUpdate = this._pendingForceUpdate || !inst.shouldComponentUpdate || inst.shouldComponentUpdate(nextProps, nextState, nextContext); if (shouldUpdate) { // 重置更新隊列 this._pendingForceUpdate = false; // 即將更新 this.props、this.state 和 this.context this._performComponentUpdate(nextParentElement, nextProps, nextState, nextContext, transaction, nextUnmaskedContext); } else { // 若是肯定組件不更新,仍然要設置 props 和 state this._currentElement = nextParentElement; this._context = nextUnmaskedContext; inst.props = nextProps; inst.state = nextState; inst.context = nextContext; } }, //當肯定組件須要更新時,則調用 _performComponentUpdate: function (nextElement, nextProps, nextState, nextContext, transaction, unmaskedContext) { var inst = this._instance; var hasComponentDidUpdate = Boolean(inst.componentDidUpdate); var prevProps; var prevState; var prevContext; // 若是存在 componentDidUpdate,則將當前的 props、state 和 context 保存一份 if (hasComponentDidUpdate) { prevProps = inst.props; prevState = inst.state; prevContext = inst.context; } // 若是存在 componentWillUpdate,則調用 if (inst.componentWillUpdate) { inst.componentWillUpdate(nextProps, nextState, nextContext); } this._currentElement = nextElement; this._context = unmaskedContext; // 更新 this.props、this.state 和 this.context inst.props = nextProps; inst.state = nextState; inst.context = nextContext; // 實現代碼省略,遞歸調用 render 渲染組件 this._updateRenderedComponent(transaction, unmaskedContext); // 當組件完成更新後,若是存在 componentDidUpdate,則調用 if (hasComponentDidUpdate) { transaction.getReactMountReady().enqueue( inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext), inst ); } } }
判斷更新階段是否須要調用componentWillReceiveProps
主要經過以下,一樣咱們只關心生命週期相關的代碼,其餘的代碼暫時不考慮:
if (this._context === nextUnmaskedContext) { nextContext = inst.context; } else { nextContext = this._processContext(nextUnmaskedContext); willReceive = true; } var prevProps = prevParentElement.props; var nextProps = nextParentElement.props; // Not a simple state update but a props update if (prevParentElement !== nextParentElement) { willReceive = true; } // 若是存在 componentWillReceiveProps,則調用 if (willReceive && inst.componentWillReceiveProps) { inst.componentWillReceiveProps(nextProps, nextContext); }
因此咱們能夠分析得出只有在context
發生變化或者parentElement
先後不一致(prevParentElement !== nextParentElement
)時,willReceive
才爲true
,這時,若是存在componentWillReceiveProps
,就會被調用。那麼咱們須要瞭解的是parentElement
存儲的是什麼信息,parentElement
存儲的信息以下:
{ $$typeof:Symbol(react.element), key:null, props:Object, ref:null, type: function Example(props), _owner:ReactCompositeComponentWrapper, _store:Object, _self:App, _source:Object, __proto__:Object }
咱們發現,parentElement
是不含父組件的state
信息的。所以咱們還能夠獲得下面的結論: 若是父組件的props
等信息發生改變時,即便這個改變的屬性沒有傳入到子組件,但也會引發子組件的componentWillReceiveProps
的執行。
而且咱們能夠發現,若是在componentWillReceiveProps
中調用setState
,state
是不會當即獲得更新。state
會在componentWillReceiveProps
後合併,因此componentWillReceiveProps
中是不能拿到新的state
。
須要注意的是
不能在
shouldComponentUpdate
和componentWillUpdate
中調用setState
,緣由是shouldComponentUpdate
與componentWillUpdate
調用setState
會致使再次調用updateComponent
,這會形成循環調用,直至耗光瀏覽器內存後崩潰。
var shouldUpdate = this._pendingForceUpdate || !inst.shouldComponentUpdate || inst.shouldComponentUpdate(nextProps, nextState, nextContext); if (shouldUpdate) { // 重置更新隊列 this._pendingForceUpdate = false; // 即將更新 this.props、this.state 和 this.context this._performComponentUpdate(nextParentElement, nextProps, nextState, nextContext, transaction, nextUnmaskedContext); } else { // 若是肯定組件不更新,仍然要設置 props 和 state this._currentElement = nextParentElement; this._context = nextUnmaskedContext; inst.props = nextProps; inst.state = nextState; inst.context = nextContext; }
而後咱們會根據shouldComponentUpdate
返回的內容,決定是否執行所有的聲明週期更新操做。若是返回false
,就不會執行接下來的更新操做。可是,從上面看得出,即便shouldComponentUpdate
返回了false
,組件中的props
和state
以及state
的都會被更新(固然,調用了forceUpdate
函數的話,會跳過shouldComponentUpdate
的判斷過程。)
若是shouldComponentUpdate
返回true
或者沒有定義shouldComponentUpdate
函數,就會進行進行組件更新。若是存在componentDidUpdate
,會將更新前的state
、props
和context
保留一份備份。若是存在componentWillUpdate
,則調用。接着遞歸調用render
進行渲染更新。當組件完成更新後,若是存在componentDidUpdate
函數就會被調用,
並將更新前的狀態備份和當前的狀態做爲參數傳遞。
var ReactCompositeComponent = { /** * 釋放由`mountComponent`分配的資源. * * @final * @internal */ unmountComponent: function(safely) { if (!this._renderedComponent) { return; } var inst = this._instance; // 若是存在 componentWillUnmount,則調用 if (inst.componentWillUnmount) { if (safely) { var name = this.getName() + '.componentWillUnmount()'; ReactErrorUtils.invokeGuardedCallback(name, inst.componentWillUnmount.bind(inst)); } else { inst.componentWillUnmount(); } } // 若是組件已經渲染,則對組件進行 unmountComponent 操做 if (this._renderedComponent) { ReactReconciler.unmountComponent(this._renderedComponent, safely); this._renderedNodeType = null; this._renderedComponent = null; this._instance = null; } // 重置相關參數、更新隊列以及更新狀態 this._pendingStateQueue = null; this._pendingReplaceState = false; this._pendingForceUpdate = false; this._pendingCallbacks = null; this._pendingElement = null; this._context = null; this._rootNodeID = null; this._topLevelWrapper = null; // 清除公共類 ReactInstanceMap.remove(inst); } }
卸載階段很是簡單,若是存在componentWillUnmount
函數,則會在更新前調用。而後遞歸調用清理渲染。最後將相關參數、更新隊列以及更新狀態進行重置爲空。
原本想接着寫一下setState
和React Transaction,發現太弱雞了,並無看懂,如今正在學習研究中,你們之後能夠關注一下~