React源碼(二)生命周期

上篇文章主要分析了React組件中,調用setState函數的過程,這篇將會開始分析React生命周期相關的內容。

眾所周知,React組件生命周期一共有三個階段:mountComponent,updateComponent,unmountComponent,同時也對應著不同的生命周期函數。

constructor:該方法是React組件的構造函數,調用它時,需要在所有語句前寫入"super(props)",否則this.props將為undefined。在該方法中不能有任何邊界操作和訂閱,就像redux推崇的那樣,這些對應操作應該在componentDidMount方法中。constructor主要用於state的初始化和事件方法的綁定,如果沒有上面的兩種操作,也可以省略掉。同時,不應該在這個方法中調用setState,但是可以將props賦值給this.state來完成state的初始化。

{ _constructComponentWithoutOwner: function( doConstruct, publicProps, publicContext, updateQueue, ) { var Component = this._currentElement.type; if (doConstruct) { if (__DEV__) { return measureLifeCyclePerf( () => new Component(publicProps, publicContext, updateQueue), this._debugID, "ctor", ); } else { return new Component(publicProps, publicContext, updateQueue); } } // This can still be an instance in case of factory components // but we"ll count this as time spent rendering as the more common case. if (__DEV__) { return measureLifeCyclePerf( () => Component(publicProps, publicContext, updateQueue), this._debugID, "render", ); } else { return Component(publicProps, publicContext, updateQueue); } }}

componentWillMount:該方法再組件掛載前調用,也就是在render之前調用,可以在該方法中調用setState,並且不會發生重渲染。

componentDidMount:該方法在組件掛載完成之後被調用,可以在該方法中獲取DOM節點,發起網路請求,執行訂閱等。在該方法中調用setState會使組件發生重渲染。

{ mountComponent: function( transaction, hostParent, hostContainerInfo, context, ) { this._context = context; this._mountOrder = nextMountID++; this._hostParent = hostParent; this._hostContainerInfo = hostContainerInfo; var publicProps = this._currentElement.props; var publicContext = this._processContext(context); var Component = this._currentElement.type; var updateQueue = transaction.getUpdateQueue(); // Initialize the public class var doConstruct = shouldConstruct(Component); var inst = this._constructComponent( doConstruct, publicProps, publicContext, updateQueue, ); var renderedElement; // Support functional components if (!doConstruct && (inst == null || inst.render == null)) { renderedElement = inst; warnIfInvalidElement(Component, renderedElement); invariant( inst === null || inst === false || React.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); this._compositeType = CompositeTypes.StatelessFunctional; } else { if (isPureComponent(Component)) { this._compositeType = CompositeTypes.PureClass; } else { this._compositeType = CompositeTypes.ImpureClass; } } // 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 = updateQueue; this._instance = inst; // Store a reference from the instance back to the internal representation ReactInstanceMap.set(inst, this); var initialState = inst.state; if (initialState === undefined) { inst.state = initialState = null; } invariant( typeof initialState === "object" && !Array.isArray(initialState), "%s.state: must be set to an object or null", this.getName() || "ReactCompositeComponent", ); this._pendingStateQueue = null; this._pendingReplaceState = false; this._pendingForceUpdate = false; var markup; if (inst.unstable_handleError) { markup = this.performInitialMountWithErrorHandling( renderedElement, hostParent, hostContainerInfo, transaction, context, ); } else { markup = this.performInitialMount( renderedElement, hostParent, hostContainerInfo, transaction, context, ); } if (inst.componentDidMount) { if (__DEV__) { transaction.getReactMountReady().enqueue(() => { measureLifeCyclePerf( () => inst.componentDidMount(), this._debugID, "componentDidMount", ); }); } else { transaction.getReactMountReady().enqueue(inst.componentDidMount, inst); } } return markup; }, performInitialMount: function( renderedElement, hostParent, hostContainerInfo, transaction, context, ) { var inst = this._instance; var debugID = 0; if (__DEV__) { debugID = this._debugID; } if (inst.componentWillMount) { if (__DEV__) { measureLifeCyclePerf( () => inst.componentWillMount(), debugID, "componentWillMount", ); } else { inst.componentWillMount(); } // When mounting, calls to `setState` by `componentWillMount` will set // `this._pendingStateQueue` without triggering a re-render. if (this._pendingStateQueue) { inst.state = this._processPendingState(inst.props, inst.context); } } // If not a stateless component, we now render if (renderedElement === undefined) { renderedElement = this._renderValidatedComponent(); } var nodeType = ReactNodeTypes.getType(renderedElement); this._renderedNodeType = nodeType; var child = this._instantiateReactComponent( renderedElement, nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */, ); this._renderedComponent = child; var markup = ReactReconciler.mountComponent( child, transaction, hostParent, hostContainerInfo, this._processChildContext(context), debugID, ); if (__DEV__) { if (debugID !== 0) { var childDebugIDs = child._debugID !== 0 ? [child._debugID] : []; ReactInstrumentation.debugTool.onSetChildren(debugID, childDebugIDs); } } return markup; }}

上面的mountComponent方法主要涉及componentWillMount和componentDidMount的調用時機,其中的performInitialMount方法又具體涉及到componentWillMount方法的調用時機

componentWillReceiveProps:該方法在組件接收到新的props時被調用。可以在這個方法中將調用setState,將props設置成組件的state,此時不會觸發重渲染。我的理解是,這樣的操作其實比較類似於Vue中的watch屬性,對props進行監聽,並執行一定的操作。請注意,無論props是否發生修改,該方法都會被調用,所以需要如果要通過在這裡去設置state,請對this.props及nextProps進行判斷。

shouldComponentUpdate:該方法在組件接收到新的props和state時被調用,讓用戶自己去判斷是否需要更新組件。該方法默認返回true,此時會執行更新,如果返回false,則不會更新。為了提升性能,我們可以在該方法中取判斷nextState與state,nextProps與props是否相同,然後來決定是否更新。當然,判斷是一門藝術,可以直接使用PureCompnent,其會自動對state,props進行淺比較,如果相等,則不更新。當然淺比較有一定的缺陷,所以需要進行深比較。深比較不推薦使用Json.stringify將state或props序列化成字元串來進行比較,因為該操作本身就比較損耗性能,這裡我推薦使用immutable。

if (willReceive && inst.componentWillReceiveProps) { if (__DEV__) { measureLifeCyclePerf( () => inst.componentWillReceiveProps(nextProps, nextContext), this._debugID, "componentWillReceiveProps", ); } else { inst.componentWillReceiveProps(nextProps, nextContext); } } var nextState = this._processPendingState(nextProps, nextContext); var shouldUpdate = true; if (!this._pendingForceUpdate) { if (inst.shouldComponentUpdate) { if (__DEV__) { shouldUpdate = measureLifeCyclePerf( () => inst.shouldComponentUpdate(nextProps, nextState, nextContext), this._debugID, "shouldComponentUpdate", ); } else { shouldUpdate = inst.shouldComponentUpdate( nextProps, nextState, nextContext, ); } } else { if (this._compositeType === CompositeTypes.PureClass) { shouldUpdate = !shallowEqual(prevProps, nextProps) || !shallowEqual(inst.state, nextState); } } }

關於上面的這兩個生命周期的內容,可以看我上一篇文章:React源碼(一)setState

componentWillUpdate: 該方法將在組件更新之前被調用,在這個方法中不能調用setState

componentDidUpdate:該方法在組件更新完成之後調用,這個時候可以獲取DOM,發起網路請求等,就像在componentDidUpdate中一樣

{_performComponentUpdate: function( nextElement, nextProps, nextState, nextContext, transaction, unmaskedContext, ) { var inst = this._instance; var hasComponentDidUpdate = Boolean(inst.componentDidUpdate); var prevProps; var prevState; var prevContext; if (hasComponentDidUpdate) { prevProps = inst.props; prevState = inst.state; prevContext = inst.context; } if (inst.componentWillUpdate) { if (__DEV__) { measureLifeCyclePerf( () => inst.componentWillUpdate(nextProps, nextState, nextContext), this._debugID, "componentWillUpdate", ); } else { inst.componentWillUpdate(nextProps, nextState, nextContext); } } this._currentElement = nextElement; this._context = unmaskedContext; inst.props = nextProps; inst.state = nextState; inst.context = nextContext; this._updateRenderedComponent(transaction, unmaskedContext); if (hasComponentDidUpdate) { if (__DEV__) { transaction.getReactMountReady().enqueue(() => { measureLifeCyclePerf( inst.componentDidUpdate.bind( inst, prevProps, prevState, prevContext, ), this._debugID, "componentDidUpdate", ); }); } else { transaction .getReactMountReady() .enqueue( inst.componentDidUpdate.bind( inst, prevProps, prevState, prevContext, ), inst, ); } } }}

上面的_performUpdateCompoent方法,展示了與comonentWillUpdate和componentDidUpdate相關的邏輯。例如: 在componentWillUpdate前將props賦值給prevProps,在componentWillUpdate之後將nextProps賦值給props,然後調用_updateRenderedComponent方法開始執行更新,更新完成之後調用componentDidUpdate。

componentWillUnmount: 該方法將在組件被卸載以及銷毀之前被調用,這個時候可以執行一些清理工作,例如:移除定時器,取消網路請求,取消訂閱等。

{unmountComponent: function(safely) { if (!this._renderedComponent) { return; } var inst = this._instance; if (inst.componentWillUnmount && !inst._calledComponentWillUnmount) { inst._calledComponentWillUnmount = true; if (safely) { var name = this.getName() + ".componentWillUnmount()"; ReactErrorUtils.invokeGuardedCallback( name, inst.componentWillUnmount.bind(inst), ); } else { if (__DEV__) { measureLifeCyclePerf( () => inst.componentWillUnmount(), this._debugID, "componentWillUnmount", ); } else { inst.componentWillUnmount(); } } } if (this._renderedComponent) { ReactReconciler.unmountComponent(this._renderedComponent, safely); this._renderedNodeType = null; this._renderedComponent = null; this._instance = null; } // Reset pending fields // Even if this component is scheduled for another update in ReactUpdates, // it would still be ignored because these fields are reset. this._pendingStateQueue = null; this._pendingReplaceState = false; this._pendingForceUpdate = false; this._pendingCallbacks = null; this._pendingElement = null; // These fields do not really need to be reset since this object is no // longer accessible. this._context = null; this._rootNodeID = 0; this._topLevelWrapper = null; // Delete the reference from the instance to this internal representation // which allow the internals to be properly cleaned up even if the user // leaks a reference to the public instance. ReactInstanceMap.remove(inst); // Some existing components rely on inst.props even after they"ve been // destroyed (in event handlers). // TODO: inst.props = null; // TODO: inst.state = null; // TODO: inst.context = null; }}

從unmountComponent方法中可以看出,其會遞歸的調用當前組件及其子組件componentWillUnmount方法,然後將組件的上下文,狀態隊列等內容清空,然後將該組件移除。

參考資源:

React.Component - Reactreactjs.org圖標facebook/reactgithub.com圖標
推薦閱讀:

看啥雙拱門,來學 webpack 3啊
極樂技術周報(第十二期)
在移動端使用transform: translate代替top left marg等做位移有好處么 ?
手淘的Flexible方案能使用雪碧圖嗎?

TAG:React | 前端开发 | 前端框架 |