1. 初步瞭解React生命週期
React生命週期可以分爲掛載、更新、卸載三個階段。主要可以分爲兩類:
- 組件掛載和卸載;
- 組件接收新的數據和狀態時的更新;
1.1 組件的掛載
組件的掛載是最基本過程,這個過程主要做初始化。在這初始化個過程中componentWillMount會在render方法之前執行,而componentDidMount方法會在render方法之後執行。分別代表了渲染前後時刻。寫一個簡單的例子:
class Demo extends React.Component {
static propTypes = {}
static defaultProps = {}
constructor(props) {
super(props)
this.state = {}
}
componentWillMount() {}
render() {return null}
componentDidMount() {}
}
如上,這個初始化過程沒有什麼特別之處,這裏包括讀取初始state、讀取初始props、以及兩個生命週期方法componentWillMount和componentDidMount。這些都只會在組件初始化時執行一次。
1.2 組件的卸載
組件的卸載只有componentWillUnmount這個一個方法。
1.3 組件的更新
組件的更新發生在父組件傳遞props或者自身執行setState改變狀態這一系列操作的情況下。和組件更新的生命週期方法有以下幾個:
class Demo extends React.Component {
//當組件更新時會順序執行以下方法
componentWillReceiveProps(nextProps){} //
shouldComponentUpdate(nextProps, nextState) {} //返回false則停止向下執行,默認返回true
componentWillUpdate(nextProps, nextState) {}
render() {}
componentDidUpdate(prevProps, prevState) {}
}
tip: shouldComponentUpdate可以用來正確的渲染組件的。理想情況下,父級節點改變時,只會重新渲染一條鏈路上和該props相關的組件。但是默認情況下,React會渲染所有的節點,因爲shouldComponentUpdate默認返回true。
2. 深入瞭解React生命週期
前面大致介紹了組件的生命週期主要分爲三種狀態:掛載、更新、卸載。如下圖可以詳細瞭解不同狀態的執行順序:
使用ES6 classes構建組件的時候static defaultProps={}其實就是調用內部的getDefaultProps方法。constructor中的this.state={}其實就是調用內部的getInitialState方法。
2.1 詳解React生命週期
自定義組件生命週期通過3個階段進行控制:MOUNTING,RECEIVE_PROPS,UNMOUNTING,它負責通知組件當時所處的階段,應該執行生命週期中的哪個步驟。這三個階段分別對應三個方法:
2.2使用createClass創建自定義組件
createClass是創建自定義組件的入口方法,負責管理生命週期中的getDefaultProps方法。該方法在整個生命週期中只執行一次,這樣所有實例初始化的props都能共享。
通過createClass創建自定義組件,利用原型繼承ReactClassComponent父類,按順序合併mixin,設置初始化defaultProps,返回構造函數。
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;
//ReactClasses沒有構造函數,通過getInitialState和componentWillMount來代替
var initialState = this.getInitialState ? this.getInitialState() : null;
this.state = initialState;
};
//原型繼承ReactClassComponent父類
Constructor.prototype = new ReactClassComponent();
Constructor.prototype.constructor = Constructor;
Constructor.prototype.__reactAutoBindPairs = [];
//合併mixin
injectedMixins.forEach(
mixSpecIntoComponent.bind(null, Constructor)
);
mixSpecIntoComponent(Constructor, spec);
//所有mixin合併後初始化defaultProps(在生個生命週期中,defaultProps只執行一次)
if (Constructor.getDefaultProps) {
Constructor.defaultProps = Constructor.getDefaultProps();
}
//設置原型
for (var methodName in ReactClassInterface) {
if (!Constructor.prototype[methodName]) {
Constructor.prototype[methodName] = null;
}
}
//最後返回的是構造函數
return Constructor;
},
}
2.3 階段一:MOUNTING
mountComponent負責管理生命週期中的getInitialState,componentWillMount,render和componentDidMount。
由於getDefaultProps是在初始化構造函數中進行管理的,所以也是整個生命週期中最先執行的。而且只執行一次也可以理解了。
由於通過ReactCompositeComponentBase返回的是一個虛擬節點,所以需要通過 instantiate-ReactComponent去得到實例,在通過mountComponent拿到結果作爲當前自定義元素的結果。
通過mountComponent掛載組件,初始化序號,標記參數等,判斷是否爲無狀態組件,並進行對應的初始化操作,比如初始化props,context等參數。利用getInitialState獲取初始化state, 初始化更新隊列和更新狀態。
如果存在componentWillMount則執行,如果此時在componetWillMount調用setState方法,是不會觸發re-render方法,而是會進行state合併,且inst.state = this._processPendingState(inst.props, inst.context)在componentWillMount之後執行。因此在render中纔可以獲取到最新的state。
因此,React是通過更新隊列this._pendingStateQueue以及更新狀態this._pendingReeplaceState和this._pendingForUpdate來實現setState的異步更新。
當渲染完成後,若存在componentDidMount則調用。
其實mountComponent是通過遞歸渲染內容。由於遞歸的特性,父組件的componentWillMount在其子組件的componentWillMount之前調用,父組件的componentDidMount在其子組件的componentDidMount之後調用。
//react/src/renderers/shared/reconciler/ReactCompositeComponent.js
//當組件掛載時,會分配一個遞增編號,表示執行ReactUpdates時更新組件的順序
var nextMountID = 1
var ReactCompositeComponentMixin = {
//初始化組件,渲染標記,註冊事件監聽器
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;
var renderedElement;
//這裏判斷是否是無狀態組件,無狀態組件沒有更新狀態序列,只關注更新
if (Component.prototype && Component.prototype.isReactComponent) {
inst = new Component(publicProps, publicContext, ReactUpdateQueue);
} else {
inst = Component(publicProps, publicContext, ReactUpdateQueue);
if (inst == null || inst.render == null) {
renderedElement = inst;
warnIfInvalidElement(Component, renderedElement);
invariant(
inst === null ||
inst === false ||
ReactElement.isValidElement(inst),
'%s(...): A valid React element (or null) must be returned. You may have ' +
'returned undefined, an array or some other invalid object.',
Component.displayName || Component.name || 'Component'
);
inst = new StatelessComponent(Component);
}
}
// These should be set up in the constructor, but as a convenience for
// simpler class abstractions, we set them up after the fact.
//這些初始化參數應該在構造函數中設置,再此處設置爲了便於簡單的類抽象
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;
}
//初始化state更新隊列
this._pendingStateQueue = null;
this._pendingReplaceState = false;
this._pendingForceUpdate = false;
var markup;
//如果掛載錯誤則執行performInitialMountWithErrorHandling(方法如下)
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) {
// Roll back to checkpoint, handle error (which may add items to the transaction), and take a new checkpoint
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
);
//遞歸渲染
var markup = ReactReconciler.mountComponent(
this._renderedComponent,
transaction,
nativeParent,
nativeContainerInfo,
this._processChildContext(context)
);
return markup;
}
}
2.4 階段二:REVEIVE_PROPS
updateComponent負責管理生命週期的componentWillReceiveProps、shouldComponent、componentWillUpdate、render、componentDidUpdate。
首先通過updateComponent更新組件,如果前後元素不一致,說明需要組件更新。
若存在componentWillReceiveProps,則執行。如果此時在componentWillReceiveProps中調用setState是不會觸發re-render,而是會進行state合併。且在componentWillReceiveProps,shouldComponentUpate和componentWillUpdate是無法獲取更新後的this.state。需要設置inst.state = nextState後纔可以。因此只有在render和componentDidUpdate中纔可以獲取更新後的state.
調用shouldComponentUpdate判斷是否需要進行組件更新,如果存在componentWillUpdate則執行。
updateComponet也是通過遞歸渲染的,由於遞歸的特性,父組件的componentWillUpdate在子組件之前執行,父組件的componentDidUpdate在其子組件之後執行。
2.5 階段三:UNMOUNTING
unmountComponent負責管理componentWillUnmount。在這個階段會清空一切。
//組件卸載
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);
},