1 React生命周期流程
調(diào)用流程可以參看上圖勘天。分為實(shí)例化,存在期和銷毀三個不同階段捉邢。介紹生命周期流程的文章很多脯丝,相信大部分同學(xué)也有所了解,我們就不詳細(xì)分析了伏伐。很多同學(xué)肯定有疑問宠进,這些方法在React內(nèi)部是在哪些方法中被調(diào)用的呢,他們觸發(fā)的時機(jī)又是什么時候呢藐翎。下面我們來詳細(xì)分析材蹬。
2 實(shí)例化生命周期
getDefaultProps
在React.creatClass()初始化組件類時实幕,會調(diào)用getDefaultProps(),將返回的默認(rèn)屬性掛載到defaultProps變量下堤器。這段代碼之前已經(jīng)分析過了昆庇,參考
React源碼分析1 — 組件和對象的創(chuàng)建(createClass,createElement).
這里要提的一點(diǎn)是闸溃,初始化組件類只運(yùn)行一次整吆。可以把它簡單類比為Java中的Class對象辉川。初始化組件類就是ClassLoader加載Class對象的過程表蝙。類對象的初始化不要錯誤理解成了實(shí)例對象的初始化。一個React組件類可能會在JSX中被多次調(diào)用员串,產(chǎn)生多個組件對象勇哗,但它只有一個類對象,也就是類加載后getDefaultProps就不會再調(diào)用了寸齐。
getInitialState
這個方法在創(chuàng)建組件實(shí)例對象的時候被調(diào)用欲诺,具體代碼位于React.creatClass()的Constructor函數(shù)中。之前文章中已經(jīng)分析了渺鹦,參考
React源碼分析1 — 組件和對象的創(chuàng)建(createClass扰法,createElement)。
mountComponent
componentWillMount毅厚,render塞颁,componentDidMount都是在mountComponent中被調(diào)用。在
React源碼分析2 — React組件插入DOM流程
一文中吸耿,我們講過mountComponent被調(diào)用的時機(jī)祠锣。它是在渲染新的ReactComponent中被調(diào)用的。輸入ReactComponent咽安,返回組件對應(yīng)的HTML伴网。把這個HTML插入到DOM中,就可以生成組件對應(yīng)的DOM對象了妆棒。所以mountComponent尤其關(guān)鍵澡腾。不同的React組件的mountComponent實(shí)現(xiàn)都有所區(qū)別。下面我們來重點(diǎn)分析React自定義組件類糕珊,也就是ReactCompositeComponent的mountComponent动分。
mountComponent: function (transaction, nativeParent, nativeContainerInfo, context) {
this._context = context;
this._mountOrder = nextMountID++;
this._nativeParent = nativeParent;
this._nativeContainerInfo = nativeContainerInfo;
// 做propTypes是否合法的判斷,這個只在開發(fā)階段有用
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;
// inst或者inst.render為空對應(yīng)的是stateless組件红选,也就是無狀態(tài)組件
// 無狀態(tài)組件沒有實(shí)例對象澜公,它本質(zhì)上只是一個返回JSX的函數(shù)而已。是一種輕量級的React組件
if (!shouldConstruct(Component) && (inst == null || inst.render == null)) {
renderedElement = inst;
warnIfInvalidElement(Component, renderedElement);
inst = new StatelessComponent(Component);
}
// 設(shè)置變量
inst.props = publicProps;
inst.context = publicContext;
inst.refs = emptyObject;
inst.updater = ReactUpdateQueue;
this._instance = inst;
// 存儲實(shí)例對象的引用到map中纠脾,方便以后查找
ReactInstanceMap.set(inst, this);
// 初始化state玛瘸,隊(duì)列等
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) {
// 掛載時出錯蜕青,進(jìn)行一些錯誤處理,然后performInitialMount糊渊,初始化掛載
markup = this.performInitialMountWithErrorHandling(renderedElement, nativeParent, nativeContainerInfo, transaction, context);
} else {
// 初始化掛載
markup = this.performInitialMount(renderedElement, nativeParent, nativeContainerInfo, transaction, context);
}
if (inst.componentDidMount) {
// 調(diào)用componentDidMount右核,以事務(wù)的形式
transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
}
return markup;
},
mountComponent先做實(shí)例對象的props,state等初始化,然后調(diào)用performInitialMount初始化掛載渺绒,完成后調(diào)用componentDidMount贺喝。這個調(diào)用鏈還是很清晰的。下面我們重點(diǎn)來分析performInitialMountWithErrorHandling和performInitialMount
performInitialMountWithErrorHandling: function (renderedElement, nativeParent, nativeContainerInfo, transaction, context) {
var markup;
var checkpoint = transaction.checkpoint();
try {
// 放到try-catch中宗兼,如果沒有出錯則調(diào)用performInitialMount初始化掛載剔宪∶浣溃可見這里沒有什么特別的操作塔插,也就是做一些錯誤處理而已
markup = this.performInitialMount(renderedElement, nativeParent, nativeContainerInfo, transaction, context);
} catch (e) {
// handleError桐经,卸載組件,然后重新performInitialMount初始化掛載
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();
this._renderedComponent.unmountComponent(true);
transaction.rollback(checkpoint);
markup = this.performInitialMount(renderedElement, nativeParent, nativeContainerInfo, transaction, context);
}
return markup;
},
可見performInitialMountWithErrorHandling只是多了一層錯誤處理而已主到,關(guān)鍵還是在performInitialMount中茶行。
performInitialMount: function (renderedElement, nativeParent, nativeContainerInfo, transaction, context) {
var inst = this._instance;
if (inst.componentWillMount) {
// render前調(diào)用componentWillMount
inst.componentWillMount();
// 將state提前合并,故在componentWillMount中調(diào)用setState不會觸發(fā)重新render登钥,而是做一次state合并畔师。這樣做的目的是減少不必要的重新渲染
if (this._pendingStateQueue) {
inst.state = this._processPendingState(inst.props, inst.context);
}
}
// 如果不是stateless,即無狀態(tài)組件牧牢,則調(diào)用render看锉,返回ReactElement
if (renderedElement === undefined) {
renderedElement = this._renderValidatedComponent();
}
// 得到組件類型,如空組件ReactNodeTypes.EMPTY塔鳍,自定義React組件ReactNodeTypes.COMPOSITE伯铣,DOM原生組件ReactNodeTypes.NATIVE
this._renderedNodeType = ReactNodeTypes.getType(renderedElement);
// 由ReactElement生成ReactComponent,這個方法在之前講解過轮纫。根據(jù)不同type創(chuàng)建不同Component對象
// 參考 http://blog.csdn.net/u013510838/article/details/55669769
this._renderedComponent = this._instantiateReactComponent(renderedElement);
// 遞歸渲染懂傀,渲染子組件
var markup = ReactReconciler.mountComponent(this._renderedComponent, transaction, nativeParent, nativeContainerInfo, this._processChildContext(context));
return markup;
},
performInitialMount中先調(diào)用componentWillMount(),再將setState()產(chǎn)生的state改變進(jìn)行state合并蜡感,然后調(diào)用_renderValidatedComponent()返回ReactElement,它會調(diào)用render()方法恃泪。然后由ReactElement創(chuàng)建ReactComponent郑兴。最后進(jìn)行遞歸渲染。下面來看renderValidatedComponent()
_renderValidatedComponent: function () {
var renderedComponent;
ReactCurrentOwner.current = this;
try {
renderedComponent = this._renderValidatedComponentWithoutOwnerOrContext();
} finally {
ReactCurrentOwner.current = null;
}
!(
return renderedComponent;
},
_renderValidatedComponentWithoutOwnerOrContext: function () {
var inst = this._instance;
// 調(diào)用render方法贝乎,得到ReactElement情连。JSX經(jīng)過babel轉(zhuǎn)譯后其實(shí)就是createElement()方法。這一點(diǎn)在前面也講解過
var renderedComponent = inst.render();
return renderedComponent;
},
3 存在期生命周期
組件實(shí)例對象已經(jīng)生成時览效,我們可以通過setState()來更新組件却舀。setState機(jī)制后面會有單獨(dú)文章分析虫几,現(xiàn)在只用知道它會調(diào)用updateComponent()來完成更新即可。下面來分析updateComponent
updateComponent: function(transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext
) {
var inst = this._instance;
var willReceive = false;
var nextContext;
var nextProps;
// context對象如果有改動,則檢查propTypes等,這在開發(fā)階段可以報錯提醒
if (this._context === nextUnmaskedContext) {
nextContext = inst.context;
} else {
nextContext = this._processContext(nextUnmaskedContext);
willReceive = true;
}
// 如果父元素類型相同,則跳過propTypes類型檢查
if (prevParentElement === nextParentElement) {
nextProps = nextParentElement.props;
} else {
nextProps = this._processProps(nextParentElement.props);
willReceive = true;
}
// 調(diào)用componentWillReceiveProps,如果通過setState進(jìn)入的updateComponent挽拔,則沒有這一步
if (willReceive && inst.componentWillReceiveProps) {
inst.componentWillReceiveProps(nextProps, nextContext);
}
// 提前合并state,componentWillReceiveProps中調(diào)用setState不會重新渲染,在此處做合并即可,因?yàn)楹竺嬉彩且{(diào)用render的
// 這樣可以避免沒必要的渲染
var nextState = this._processPendingState(nextProps, nextContext);
// 調(diào)用shouldComponentUpdate給shouldUpdate賦值
// 如果通過forceUpdate進(jìn)入的updateComponent辆脸,即_pendingForceUpdate不為空,則不用判斷shouldComponentUpdate.
var shouldUpdate = true;
if (!this._pendingForceUpdate && inst.shouldComponentUpdate) {
shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext);
}
// 如果shouldUpdate為true,則會執(zhí)行渲染,否則不會
this._updateBatchNumber = null;
if (shouldUpdate) {
this._pendingForceUpdate = false;
// 執(zhí)行更新渲染,后面詳細(xì)分析
this._performComponentUpdate(
nextParentElement,
nextProps,
nextState,
nextContext,
transaction,
nextUnmaskedContext
);
} else {
// shouldUpdate為false,則不會更新渲染
this._currentElement = nextParentElement;
this._context = nextUnmaskedContext;
inst.props = nextProps;
inst.state = nextState;
inst.context = nextContext;
}
},
updateComponent中螃诅,先調(diào)用componentWillReceiveProps啡氢,然后合并setState導(dǎo)致的state變化。然后調(diào)用shouldComponentUpdate判斷是否需要更新渲染术裸。如果需要倘是,則調(diào)用_performComponentUpdate執(zhí)行渲染更新,下面接著分析performComponentUpdate袭艺。
_performComponentUpdate: function(nextElement,nextProps,nextState,nextContext,transaction,
unmaskedContext
) {
var inst = this._instance;
// 判斷是否已經(jīng)update了
var hasComponentDidUpdate = Boolean(inst.componentDidUpdate);
var prevProps;
var prevState;
var prevContext;
if (hasComponentDidUpdate) {
prevProps = inst.props;
prevState = inst.state;
prevContext = inst.context;
}
// render前調(diào)用componentWillUpdate
if (inst.componentWillUpdate) {
inst.componentWillUpdate(nextProps, nextState, nextContext);
}
// state props等屬性設(shè)置到內(nèi)部變量inst上
this._currentElement = nextElement;
this._context = unmaskedContext;
inst.props = nextProps;
inst.state = nextState;
inst.context = nextContext;
// 內(nèi)部會調(diào)用render方法搀崭,重新解析ReactElement并得到HTML
this._updateRenderedComponent(transaction, unmaskedContext);
// render后調(diào)用componentDidUpdate
if (hasComponentDidUpdate) {
transaction.getReactMountReady().enqueue(
inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext),
inst
);
}
},
_performComponentUpdate會調(diào)用componentWillUpdate,然后在調(diào)用updateRenderedComponent進(jìn)行更新渲染猾编,最后調(diào)用componentDidUpdate瘤睹。下面來看看updateRenderedComponent中怎么調(diào)用render方法的。
_updateRenderedComponent: function(transaction, context) {
var prevComponentInstance = this._renderedComponent;
var prevRenderedElement = prevComponentInstance._currentElement;
// _renderValidatedComponent內(nèi)部會調(diào)用render,得到ReactElement
var nextRenderedElement = this._renderValidatedComponent();
// 判斷是否做DOM diff袍镀。React為了簡化遞歸diff,認(rèn)為組件層級不變,且type和key不變(key用于listView等組件,很多時候我們沒有設(shè)置type)才update,否則先unmount再重新mount
if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) {
// 遞歸updateComponent,更新子組件的Virtual DOM
ReactReconciler.receiveComponent(prevComponentInstance, nextRenderedElement, transaction, this._processChildContext(context));
} else {
var oldNativeNode = ReactReconciler.getNativeNode(prevComponentInstance);
// 不做DOM diff,則先卸載掉,然后再加載默蚌。也就是先unMountComponent,再mountComponent
ReactReconciler.unmountComponent(prevComponentInstance, false);
this._renderedNodeType = ReactNodeTypes.getType(nextRenderedElement);
// 由ReactElement創(chuàng)建ReactComponent
this._renderedComponent = this._instantiateReactComponent(nextRenderedElement);
// mountComponent掛載組件,得到組件對應(yīng)HTML
var nextMarkup = ReactReconciler.mountComponent(this._renderedComponent, transaction, this._nativeParent, this._nativeContainerInfo, this._processChildContext(context));
// 將HTML插入DOM中
this._replaceNodeWithMarkup(oldNativeNode, nextMarkup, prevComponentInstance);
}
},
_renderValidatedComponent: function() {
var renderedComponent;
ReactCurrentOwner.current = this;
try {
renderedComponent =
this._renderValidatedComponentWithoutOwnerOrContext();
} finally {
ReactCurrentOwner.current = null;
}
return renderedComponent;
},
_renderValidatedComponentWithoutOwnerOrContext: function() {
var inst = this._instance;
// 看到render方法了把,應(yīng)該放心了把~
var renderedComponent = inst.render();
return renderedComponent;
},
和mountComponent中一樣苇羡,updateComponent也是用遞歸的方式將各子組件進(jìn)行update的绸吸。這里要特別注意的是DOM diff。DOM diff是React中渲染加速的關(guān)鍵所在设江,它會幫我們算出virtual DOM中真正變化的部分锦茁,并對這部分進(jìn)行原生DOM操作。為了避免循環(huán)遞歸對比節(jié)點(diǎn)的低效率叉存,React中做了假設(shè)码俩,即只對層級不變,type不變歼捏,key不變的組件進(jìn)行Virtual DOM更新稿存。這其中的關(guān)鍵是shouldUpdateReactComponent,下面分析
function shouldUpdateReactComponent(prevElement, nextElement) {
var prevEmpty = prevElement === null || prevElement === false;
var nextEmpty = nextElement === null || nextElement === false;
if (prevEmpty || nextEmpty) {
return prevEmpty === nextEmpty;
}
var prevType = typeof prevElement;
var nextType = typeof nextElement;
// React DOM diff算法
// 如果前后兩次為數(shù)字或者字符,則認(rèn)為只需要update(處理文本元素)
// 如果前后兩次為DOM元素或React元素,則必須在同一層級內(nèi),且type和key不變(key用于listView等組件,很多時候我們沒有設(shè)置type)才update,否則先unmount再重新mount
if (prevType === 'string' || prevType === 'number') {
return (nextType === 'string' || nextType === 'number');
} else {
return (
nextType === 'object' &&
prevElement.type === nextElement.type &&
prevElement.key === nextElement.key
);
}
}
4 銷毀
前面提到過瞳秽,更新組件時瓣履,如果不滿足DOM diff條件,會先unmountComponent, 然后再mountComponent练俐,下面我們來分析下unmountComponent時都發(fā)生了什么事袖迎。和mountComponent的多態(tài)一樣,不同type的ReactComponent也會有不同的unmountComponent行為。我們來分析下React自定義組件燕锥,也就是ReactCompositeComponent中的unmountComponent辜贵。
unmountComponent: function(safely) {
if (!this._renderedComponent) {
return;
}
var inst = this._instance;
// 調(diào)用componentWillUnmount
if (inst.componentWillUnmount && !inst._calledComponentWillUnmount) {
inst._calledComponentWillUnmount = true;
// 安全模式下,將componentWillUnmount包在try-catch中归形。否則直接componentWillUnmount
if (safely) {
var name = this.getName() + '.componentWillUnmount()';
ReactErrorUtils.invokeGuardedCallback(name, inst.componentWillUnmount.bind(inst));
} else {
inst.componentWillUnmount();
}
}
// 遞歸調(diào)用unMountComponent來銷毀子組件
if (this._renderedComponent) {
ReactReconciler.unmountComponent(this._renderedComponent, safely);
this._renderedNodeType = null;
this._renderedComponent = null;
this._instance = null;
}
// reset等待隊(duì)列和其他等待狀態(tài)
this._pendingStateQueue = null;
this._pendingReplaceState = false;
this._pendingForceUpdate = false;
this._pendingCallbacks = null;
this._pendingElement = null;
// reset內(nèi)部變量,防止內(nèi)存泄漏
this._context = null;
this._rootNodeID = null;
this._topLevelWrapper = null;
// 將組件從map中移除,還記得我們在mountComponent中將它加入了map中的吧
ReactInstanceMap.remove(inst);
},
可見托慨,unmountComponent還是比較簡單的,它就做三件事
調(diào)用componentWillUnmount()
遞歸調(diào)用unmountComponent(),銷毀子組件
將內(nèi)部變量置空连霉,防止內(nèi)存泄漏
5 總結(jié)
React自定義組件創(chuàng)建期榴芳,存在期,銷毀期三個階段的生命周期調(diào)用上面都講完了跺撼。三個入口函數(shù)mountComponent窟感,updateComponent,unmountComponent尤其關(guān)鍵歉井。大家如果有興趣柿祈,還可以自行分析ReactDOMEmptyComponent,ReactDOMComponent和ReactDOMTextComponent的這三個方法哩至。
深入學(xué)習(xí)React生命周期源碼可以幫我們理清各個方法的調(diào)用順序躏嚎,明白它們都是什么時候被調(diào)用的,哪些條件下才會被調(diào)用等等菩貌。閱讀源碼雖然有點(diǎn)枯燥卢佣,但能夠大大加深對上層API接口的理解,并體會設(shè)計者設(shè)計這些API的良苦用心箭阶。