React生命周期浅谈
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生命周期
初次渲染
//本文代码基于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的原型中,以缩短再次查找方法的时间。
在这个函数中,我们关心的部分其实主要集中在:
if (Constructor.getDefaultProps) { Constructor.defaultProps = Constructor.getDefaultProps(); }
我们发现在调用React.createClass,已经执行了getDefaultProps(),并将其赋值于Constructor的原型中,所以我们对照声明周期图可以得到:
React中的getDefaultProps()仅会在整个生命周期中只执行一次,并且初始化的实例都会共享该defaultProps
ReactCompositeComponent中的mountComponent、updateComponent、unmountComponent分别对应于React中mount、update、unmount阶段的处理,首先大致看一下mount阶段的简要代码:
// 当组件挂载时,会分配一个递增编号,表示执行 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。
// 如果存在 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。并且由于渲染的过程都是递归的,我们可以综合得到渲染阶段的生命周期(包括子节点)如下:
更新阶段
首先还是看一下简要的更新阶段的代码:
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存储的信息如下:
{ $$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。
需要注意的是
不能在 shouldComponentUpdate 和 componentWillUpdate 中调用 setState,原因是shouldComponentUpdate与componentWillUpdate调用setState会导致再次调用updateComponent,这会造成循环调用,直至耗光浏览器内存后崩溃。
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函数就会被调用,
并将更新前的状态备份和当前的状态作为参数传递。
卸载阶段
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,发现太弱鸡了,并没有看懂,现在正在学习研究中,大家以后可以关注一下~
相关推荐
-
Vue.js中,如何自己维护路由跳转记录? 框架
2019-6-8
-
通过路由url携带参数进行参数传递 框架
2019-6-2
-
Vue 页面权限控制和登陆验证 框架
2019-6-22
-
vue-cli – webpack 打包兼容 360 浏览器和 IE 浏览器 框架
2019-8-18
-
剖析Vue原理&实现双向绑定MVVM 框架
2018-9-16
-
redux-saga框架使用详解及Demo教程 框架
2019-3-11
-
Nodejs进阶:crypto模块中你需要掌握的安全基础知识 框架
2019-5-12
-
基于Common.Logging + Log4Net实现的日志管理 框架
2019-7-8
-
路由场景下父子组件的生命周期顺序来个刨根问底 框架
2018-12-17
-
前端进阶之路:如何高质量完成产品需求开发 框架
2019-5-12