目前,前端領域中 React 勢頭正盛,不多可以深刻剖析內部實現機制和原理。本系列文章但願經過剖析 React 源碼,理解其內部的實現原理,知其然更要知其因此然。javascript
對於 React,其組件生命週期(Component Lifecycle)是它的核心概念,本文從源碼入手,來剖析 React 生命週期的管理藝術。html
閱讀本文須要對 React 有必定的瞭解,若是你不知何爲組件的生命週期,請詳讀 React 生命週期的文檔。前端
若是你對 React 組件的生命週期存在些許疑惑,如生命週期如何順序管理;setState
如何實現異步操做,又是什麼時候真正更新等,那麼本文值得閱讀。java
React 的主要思想是經過構建可複用組件來構建用戶界面。所謂組件其實就是 有限狀態機,經過狀態渲染對應的界面,且每一個組件都有本身的生命週期,它規定了組件的狀態和方法須要在哪一個階段進行改變和執行。react
有限狀態機(FSM),表示有限個狀態以及在這些狀態之間的轉移和動做等行爲的模型。通常經過狀態、事件、轉換和動做來描述有限狀態機,下面是描述組合鎖狀態機的模型圖,包括5個狀態、5個狀態自轉換、6個狀態間轉換和1個復位 RESET 轉換到狀態 S1。狀態機,可以記住目前所處的狀態,根據當前的狀態能夠作出相應的決策,而且在進入不一樣的狀態時,能夠作不一樣的操做。經過狀態機將複雜的關係簡單化,利用這種天然而直觀的方式可讓代碼更容易理解。git
React 正是利用這一律念,經過管理狀態來實現對組件的管理。例如,某個組件有顯示和隱藏兩個狀態,一般會設計兩個方法 show()
和 hide()
來實現切換;而 React 只須要設置狀態 setState({ showed: true/false })
便可實現。同時,React 還引入了組件的生命週期概念。經過它就能夠實現組件的狀態機控制,從而達到 「生命週期-狀態-組件」 的和諧畫面。github
雖然組件、狀態機、生命週期這三者都不是 React 首創,若是熟悉 Web Components 標準,它與其中的自定義組件的生命週期的概念類似。但就目前而言,React 是將這三種概念結合地相對清晰流暢的界面庫。api
在自定義 React 組件時,根據須要會在組件生命週期的不一樣階段實現不一樣的邏輯。爲了查看 組件生命週期的執行順序,你可使用 react-lifecycle mixin,將此 mixin 添加到須要觀察的組件中,當任何生命週期方法被調用時,都能在控制檯觀察到對應的生命週期的調用時狀態。瀏覽器
// react-lifecycle mixin import React from 'react'; import ReactDom from 'react-dom'; import LifeCycle from 'react-lifecycle'; const body = document.body; const MyComponent = React.createClass({ mixins: [LifeCycle], render() { console.log('render'); return null; } }); ReactDom.render(<MyComponent />, body); ReactDom.unmountComponentAtNode(body); ReactDom.render(<MyComponent />, body); ReactDom.render(<MyComponent />, body);
經過反覆試驗,獲得了組件的生命週期在不一樣狀態下的執行順序:dom
當首次裝載組件時,按順序執行 getDefaultProps、getInitialState、componentWillMount、render 和 componentDidMount;
當卸載組件時,執行 componentWillUnmount;
當從新裝載組件時,此時按順序執行 getInitialState、componentWillMount、render 和 componentDidMount,但並不執行 getDefaultProps;
當再次渲染組件時,組件接受到更新狀態,此時按順序執行 componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render 和 componentDidUpdate。
爲什麼 React 會按上述順序執行生命週期?
爲什麼 React 屢次 render 時,會執行生命週期的不一樣階段?
爲什麼 getDefaultProps 只執行了1次?
自定義組件(ReactCompositeComponent)的生命週期主要經過三種狀態進行管理:MOUNTING、RECEIVE_PROPS、UNMOUNTING,它們負責通知組件當前所處的狀態,應該執行生命週期中的哪一個步驟,是否能夠更新 state。三個狀態對應三種方法,分別爲:mountComponent、updateComponent、unmountComponent,每一個方法都提供了兩種處理方法,will 方法在進入狀態以前調用,did 方法在進入狀態以後調用,三種狀態三種方法五種處理方法,此外還提供兩種特殊狀態的處理方法。
mountComponent -> MOUNTING
updateComponent -> RECEIVE_PROPS
unmountComponent -> UNMOUNTING
createClass 建立自定義組件的入口方法,負責管理生命週期中的 getDefaultProps。getDefaultProps 方法只執行一次,這樣全部實例初始化的 props 將會被共享。
經過 createClass 建立自定義組件,利用原型繼承 ReactCompositeComponentBase 父類,按順序合併 mixins,設置初始化 defaultProps,建立元素 ReactElement。
// ReactCompositeComponent 的基類 var ReactCompositeComponentBase = function() {}; // 將 Mixin 合併到 ReactCompositeComponentBase 的原型上 assign( ReactCompositeComponentBase.prototype, ReactComponent.Mixin, ReactOwner.Mixin, ReactPropTransferer.Mixin, ReactCompositeComponentMixin ); var ReactCompositeComponent = { LifeCycle: CompositeLifeCycle, Base: ReactCompositeComponentBase, // 建立組件 createClass: function(spec) { // 構造函數 var Constructor = function(props, context) { this.props = props; this.context = context; this.state = null; var initialState = this.getInitialState ? this.getInitialState() : null; this.state = initialState; }; // 原型繼承父類 Constructor.prototype = new ReactCompositeComponentBase(); Constructor.prototype.constructor = Constructor; // 合併 mixins injectedMixins.forEach( mixSpecIntoComponent.bind(null, Constructor) ); mixSpecIntoComponent(Constructor, spec); // mixins 合併後裝載 defaultProps (React整個生命週期中 getDefaultProps 只執行一次) if (Constructor.getDefaultProps) { Constructor.defaultProps = Constructor.getDefaultProps(); } for (var methodName in ReactCompositeComponentInterface) { if (!Constructor.prototype[methodName]) { Constructor.prototype[methodName] = null; } } return ReactElement.createFactory(Constructor); } }
mountComponent 負責管理生命週期中的 getInitialState、componentWillMount、render 和 componentDidMount。
因爲 getDefaultProps 是經過 Constructor 進行管理,所以也是整個生命週期中最早開始執行,而 mountComponent 只能望洋興嘆,沒法調用到 getDefaultProps。這就解釋了爲什麼 getDefaultProps 只執行1次的緣由。
因爲經過 ReactCompositeComponentBase 返回的是一個虛擬節點,所以須要利用 instantiateReactComponent 去獲得實例,再使用 mountComponent 拿到結果做爲當前自定義元素的結果。
首先經過 mountComponent 裝載組件,此時,將狀態設置爲 MOUNTING,利用 getInitialState 獲取初始化 state,初始化更新隊列。
若存在 componentWillMount,則執行;若是此時在 componentWillMount 中調用 setState,是不會觸發 reRender,而是進行 state 合併。
到此時,已經完成 MOUNTING 的工做,更新狀態爲 NULL,同時 state 也將執行更新操做,此刻在 render 中能夠獲取更新後的 this.state 數據。
其實,mountComponent 本質上是經過 遞歸渲染 內容的,因爲遞歸的特性,父組件的 componentWillMount 必定在其子組件的 componentWillMount 以前調用,而父組件的 componentDidMount 確定在其子組件的 componentDidMount 以後調用。
當渲染完成以後,若存在 componentDidMount 則觸發。這就解釋了 componentWillMount - render - componentDidMount 三者之間的執行順序。
instantiateReactComponent 經過判斷元素類型(類型包括:object、string、function)建立元素實例,這裏不作過多介紹,當講解到 React Virtual DOM 時,再詳細介紹此方法。
// 裝載組件 mountComponent: function(rootID, transaction, mountDepth) { // 當前狀態爲 MOUNTING this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING; // 當前元素對應的上下文 this.context = this._processContext(this._currentElement._context); // 當前元素對應的 props this.props = this._processProps(this.props); // 獲取初始化 state this.state = this.getInitialState(); // 初始化更新隊列 this._pendingState = null; this._pendingForceUpdate = false; // componentWillMount 調用setstate,不會觸發rerender而是自動提早合併 if (this.componentWillMount) { this.componentWillMount(); if (this._pendingState) { this.state = this._pendingState; this._pendingState = null; } } // 獲得 _currentElement 對應的 component 類實例 this._renderedComponent = instantiateReactComponent( this._renderValidatedComponent(), this._currentElement.type ); // 完成 MOUNTING,更新 state this._compositeLifeCycleState = null; // render 遞歸渲染 var markup = this._renderedComponent.mountComponent( rootID, transaction, mountDepth + 1 ); // 若是存在 this.componentDidMount,則渲染完成後觸發 if (this.componentDidMount) { transaction.getReactMountReady().enqueue(this.componentDidMount, this); } return markup; }
updateComponent 負責管理生命週期中的 componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render 和 componentDidUpdate。
首先經過 updateComponent 更新組件,若是先後元素不一致說明須要進行組件更新,此時將狀態設置爲 RECEIVING_PROPS。
若存在 componentWillReceiveProps,則執行;若是此時在 componentWillReceiveProps 中調用 setState
,是不會觸發 reRender,而是進行 state 合併。
到此時,已經完成 RECEIVING_PROPS 工做,更新狀態爲 NULL,同時 state 也將執行更新操做,此刻 this.state
能夠獲取到更新後的數據。
注意:此時
this.state
雖然獲取到更新數據,但只能在內部源碼中使用,咱們在開發時,若在 componentWillReceiveProps 中調用setState
,那麼在 componentWillReceiveProps、shouldComponentUpdate 和 componentWillUpdate 中仍是沒法獲取到更新後的this.state
,即此時訪問的this.state
仍然是未更新的數據,只有在 render 和 componentDidUpdate 中才能獲取到更新後的this.state
。
調用 shouldComponentUpdate 判斷是否須要進行組件更新,若是存在 componentWillUpdate,則執行。
updateComponent 本質上也是經過 遞歸渲染 內容的,因爲遞歸的特性,父組件的 componentWillUpdate 必定在其子組件的 componentWillUpdate 以前調用,而父組件的 componentDidUpdate 確定在其子組件 componentDidUpdate 以後調用。
當渲染完成以後,若存在 componentDidUpdate,則觸發,這就解釋了 componentWillReceiveProps - componentWillUpdate - render - componentDidUpdate 它們之間的執行順序。
注意:禁止在 shouldComponentUpdate 和 componentWillUpdate 中調用
setState
,會形成循環調用,直至耗光瀏覽器內存後崩潰。(請繼續閱讀,尋找答案)
// 更新組件 updateComponent: function(transaction, prevParentElement, nextParentElement) { var prevContext = this.context; var prevProps = this.props; var nextContext = prevContext; var nextProps = prevProps; if (prevParentElement !== nextParentElement) { nextContext = this._processContext(nextParentElement._context); nextProps = this._processProps(nextParentElement.props); // 當前狀態爲 RECEIVING_PROPS this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS; // 若是存在 componentWillReceiveProps,則執行 if (this.componentWillReceiveProps) { this.componentWillReceiveProps(nextProps, nextContext); } } // 設置狀態爲 null,更新 state this._compositeLifeCycleState = null; var nextState = this._pendingState || this.state; this._pendingState = null; var shouldUpdate = this._pendingForceUpdate || !this.shouldComponentUpdate || this.shouldComponentUpdate(nextProps, nextState, nextContext); if (!shouldUpdate) { // 若是肯定組件不更新,仍然要設置 props 和 state this._currentElement = nextParentElement; this.props = nextProps; this.state = nextState; this.context = nextContext; this._owner = nextParentElement._owner; return; } this._pendingForceUpdate = false; ...... // 若是存在 componentWillUpdate,則觸發 if (this.componentWillUpdate) { this.componentWillUpdate(nextProps, nextState, nextContext); } // render 遞歸渲染 var nextMarkup = this._renderedComponent.mountComponent( thisID, transaction, this._mountDepth + 1 ); // 若是存在 componentDidUpdate,則觸發 if (this.componentDidUpdate) { transaction.getReactMountReady().enqueue( this.componentDidUpdate.bind(this, prevProps, prevState, prevContext), this ); } },
unmountComponent 負責管理生命週期中的 componentWillUnmount。
首先將狀態設置爲 UNMOUNTING,若存在 componentWillUnmount,則執行;若是此時在 componentWillUnmount 中調用 setState
,是不會觸發 reRender。更新狀態爲 NULL,完成組件卸載操做。
// 卸載組件 unmountComponent: function() { // 設置狀態爲 UNMOUNTING this._compositeLifeCycleState = CompositeLifeCycle.UNMOUNTING; // 若是存在 componentWillUnmount,則觸發 if (this.componentWillUnmount) { this.componentWillUnmount(); } // 更新狀態爲 null this._compositeLifeCycleState = null; this._renderedComponent.unmountComponent(); this._renderedComponent = null; ReactComponent.Mixin.unmountComponent.call(this); }
當調用 setState
時,會對 state 以及 _pendingState 更新隊列進行合併操做,但其實真正更新 state 的幕後黑手是 replaceState。
replaceState 會先判斷當前狀態是否爲 MOUNTING,若是不是即會調用 ReactUpdates.enqueueUpdate
執行更新。
當狀態不爲 MOUNTING 或 RECEIVING_PROPS 時,performUpdateIfNecessary 會獲取 _pendingElement、_pendingState、_pendingForceUpdate,並調用 updateComponent 進行組件更新。
若是在 shouldComponentUpdate 或 componentWillUpdate 中調用 setState
,此時的狀態已經從 RECEIVING_PROPS -> NULL,則 performUpdateIfNecessary 就會調用 updateComponent 進行組件更新,但 updateComponent 又會調用 shouldComponentUpdate 和 componentWillUpdate,所以形成循環調用,使得瀏覽器內存佔滿後崩潰。
// 更新 state setState: function(partialState, callback) { // 合併 _pendingState this.replaceState( assign({}, this._pendingState || this.state, partialState), callback ); }, // 更新 state replaceState: function(completeState, callback) { validateLifeCycleOnReplaceState(this); // 更新隊列 this._pendingState = completeState; // 判斷狀態是否爲 MOUNTING,若是不是,便可執行更新 if (this._compositeLifeCycleState !== CompositeLifeCycle.MOUNTING) { ReactUpdates.enqueueUpdate(this, callback); } }, // 若是存在 _pendingElement、_pendingState、_pendingForceUpdate,則更新組件 performUpdateIfNecessary: function(transaction) { var compositeLifeCycleState = this._compositeLifeCycleState; // 當狀態爲 MOUNTING 或 RECEIVING_PROPS時,則不更新 if (compositeLifeCycleState === CompositeLifeCycle.MOUNTING || compositeLifeCycleState === CompositeLifeCycle.RECEIVING_PROPS) { return; } var prevElement = this._currentElement; var nextElement = prevElement; if (this._pendingElement != null) { nextElement = this._pendingElement; this._pendingElement = null; } // 調用 updateComponent this.updateComponent( transaction, prevElement, nextElement ); }
React 經過三種狀態:MOUNTING、RECEIVE_PROPS、UNMOUNTING,管理整個生命週期的執行順序;
setState 會先進行 _pendingState 更新隊列的合併操做,不會馬上 reRender,所以是異步操做,且經過判斷狀態(MOUNTING、RECEIVE_PROPS)來控制 reRender 的時機;
不建議在 getDefaultProps、getInitialState、shouldComponentUpdate、componentWillUpdate、render 和 componentWillUnmount 中調用 setState
,特別注意:不能在 shouldComponentUpdate 和 componentWillUpdate 中調用 setState
,會致使循環調用。
若是本文可以爲你解決些許關於 React 生命週期管理的疑惑,請點個贊吧!