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,发现太弱鸡了,并没有看懂,现在正在学习研究中,大家以后可以关注一下~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。