Open MrErHu opened 7 years ago
React学习过程中,对于组件最重要的(也可能是之一)的莫过于是组件的生命周期了,React相当于使用状态来映射到界面输出,通过状态的改变从而改变界面效果。在状态的改变过程中,必须要经历组件的生命周期。
React会经历三个阶段:mount、update、unmount,每个阶段对应两个生命周期(ummount除外):Will(对应进入)与Did(对应结束),因而存在五个对应的方法,并且在update阶段存在两种特殊的方法:shouldComponentUpdate与componentWillReceiveProps,这 些函数基本构成了React的生命周期。如下图所示:
shouldComponentUpdate
componentWillReceiveProps
上图中的getDefaultProps和getInitialState分别对应ES6中的static defaultProps = {}与构造函数construct中的this.state ={}赋值。下面我们按照上图的过程依次介绍:(介绍主要以React.createClass为例,基本等同于extends React.Component)
getDefaultProps
getInitialState
static defaultProps = {}
construct
this.state ={}
React.createClass
extends React.Component
//本文代码基于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的原型中,以缩短再次查找方法的时间。 在这个函数中,我们关心的部分其实主要集中在:
Constructor(props, context, updater)
defaultProps
Constructor
if (Constructor.getDefaultProps) { Constructor.defaultProps = Constructor.getDefaultProps(); }
我们发现在调用React.createClass,已经执行了getDefaultProps(),并将其赋值于Constructor的原型中,所以我们对照声明周期图可以得到:
getDefaultProps()
React中的getDefaultProps()仅会在整个生命周期中只执行一次,并且初始化的实例都会共享该defaultProps
ReactCompositeComponent中的mountComponent、updateComponent、unmountComponent分别对应于React中mount、update、unmount阶段的处理,首先大致看一下mount阶段的简要代码:
ReactCompositeComponent
mountComponent
updateComponent
unmountComponent
// 当组件挂载时,会分配一个递增编号,表示执行 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。
state
performInitialMountWithErrorHandling
performInitialMount
// 如果存在 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。并且由于渲染的过程都是递归的,我们可以综合得到渲染阶段的生命周期(包括子节点)如下:
componentWillMount
setState
markup
首先还是看一下简要的更新阶段的代码:
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存储的信息如下:
context
parentElement
prevParentElement !== nextParentElement
willReceive
true
{ $$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。
props
需要注意的是 不能在 shouldComponentUpdate 和 componentWillUpdate 中调用 setState,原因是shouldComponentUpdate与componentWillUpdate调用setState会导致再次调用updateComponent,这会造成循环调用,直至耗光浏览器内存后崩溃。
需要注意的是
不能在 shouldComponentUpdate 和 componentWillUpdate 中调用 setState,原因是shouldComponentUpdate与componentWillUpdate调用setState会导致再次调用updateComponent,这会造成循环调用,直至耗光浏览器内存后崩溃。
componentWillUpdate
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函数就会被调用, 并将更新前的状态备份和当前的状态作为参数传递。
false
forceUpdate
componentDidUpdate
render
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,发现太弱鸡了,并没有看懂,现在正在学习研究中,大家以后可以关注一下~
componentWillUnmount
React学习过程中,对于组件最重要的(也可能是之一)的莫过于是组件的生命周期了,React相当于使用状态来映射到界面输出,通过状态的改变从而改变界面效果。在状态的改变过程中,必须要经历组件的生命周期。
React会经历三个阶段:mount、update、unmount,每个阶段对应两个生命周期(ummount除外):Will(对应进入)与Did(对应结束),因而存在五个对应的方法,并且在update阶段存在两种特殊的方法:
shouldComponentUpdate
与componentWillReceiveProps
,这 些函数基本构成了React的生命周期。如下图所示:上图中的
getDefaultProps
和getInitialState
分别对应ES6中的static defaultProps = {}
与构造函数construct
中的this.state ={}
赋值。下面我们按照上图的过程依次介绍:(介绍主要以React.createClass
为例,基本等同于extends React.Component
)React生命周期
初次渲染
总结一下上面的代码
React.createClass
返回函数Constructor(props, context, updater)
用来生成组件的实例,而React.createClass
执行的时候会执行包括:合并mixin,获取默认属性defaultProps
将其赋值到Constructor
的原型中,并且也将传入React.createClass
中的方法赋值到Constructor
的原型中,以缩短再次查找方法的时间。 在这个函数中,我们关心的部分其实主要集中在:我们发现在调用
React.createClass
,已经执行了getDefaultProps()
,并将其赋值于Constructor
的原型中,所以我们对照声明周期图可以得到:ReactCompositeComponent
中的mountComponent
、updateComponent
、unmountComponent
分别对应于React中mount、update、unmount阶段的处理,首先大致看一下mount阶段的简要代码:我们现在只关心生命周期相关的代码,初始化及其他的代码暂时不考虑,我们发现初始化
state
之后会进入渲染的步骤,根据是否存在错误,选择性执行performInitialMountWithErrorHandling
与performInitialMount
,我们仅考虑正常情况下的performInitialMount
。如果存在
componentWillMount
则执行,如果在componentWillMount
执行了setState
方法,在componentWillMount
并不会得到已经更新的state
,因为我们发现的state
的合并过程是在componentWillMount
结束后才执行的。然后在performInitialMount
(为例)会进行递归渲染, 然后在递归执行结束后,返回markup
(返回的markup会被插入DOM中)。然后,如果存在 componentDidMount。并且由于渲染的过程都是递归的,我们可以综合得到渲染阶段的生命周期(包括子节点)如下:更新阶段
首先还是看一下简要的更新阶段的代码:
判断更新阶段是否需要调用
componentWillReceiveProps
主要通过如下,同样我们只关心生命周期相关的代码,其他的代码暂时不考虑:所以我们可以分析得出只有在
context
发生变化或者parentElement
前后不一致(prevParentElement !== nextParentElement
)时,willReceive
才为true
,这时,如果存在componentWillReceiveProps
,就会被调用。那么我们需要了解的是parentElement
存储的是什么信息,parentElement
存储的信息如下:我们发现,
parentElement
是不含父组件的state
信息的。因此我们还可以得到下面的结论: 如果父组件的props
等信息发生改变时,即使这个改变的属性没有传入到子组件,但也会引起子组件的componentWillReceiveProps
的执行。 并且我们可以发现,如果在componentWillReceiveProps
中调用setState
,state
是不会立即得到更新。state
会在componentWillReceiveProps
后合并,所以componentWillReceiveProps
中是不能拿到新的state
。然后我们会根据
shouldComponentUpdate
返回的内容,决定是否执行全部的声明周期更新操作。如果返回false
,就不会执行接下来的更新操作。但是,从上面看得出,即使shouldComponentUpdate
返回了false
,组件中的props
和state
以及state
的都会被更新(当然,调用了forceUpdate
函数的话,会跳过shouldComponentUpdate
的判断过程。) 如果shouldComponentUpdate
返回true
或者没有定义shouldComponentUpdate
函数,就会进行进行组件更新。如果存在componentDidUpdate
,会将更新前的state
、props
和context
保留一份备份。如果存在componentWillUpdate
,则调用。接着递归调用render
进行渲染更新。当组件完成更新后,如果存在componentDidUpdate
函数就会被调用, 并将更新前的状态备份和当前的状态作为参数传递。卸载阶段
卸载阶段非常简单,如果存在
componentWillUnmount
函数,则会在更新前调用。然后递归调用清理渲染。最后将相关参数、更新队列以及更新状态进行重置为空。 本来想接着写一下setState
和React Transaction,发现太弱鸡了,并没有看懂,现在正在学习研究中,大家以后可以关注一下~