本文使用的是react 15.6.1的代码
前言
在一个React组件中,在不同的阶段会自动调用一些方法,我们称之为生命周期,一个标准的组件如图,会有以下的生命周期
声明阶段
-
getDefaultProps
,设置组件默认的props,每个组件的一生只会执行一次,在调用createClass的时候就会调用
挂载阶段
-
constructor
组件的构造函数,一般用于设置一些初始状态 -
getInitialState
设置默认state的值 -
componentWillMount
组件渲染前调用 -
render
必须重写,组件渲染出来的内容 -
componentDidMount
组件渲染之后调用
在大多数情况下我们一般会在componentWillMount/componentDidMount
向后端请求数据以进行渲染
生存阶段
生存阶段是在组件进行更新操作(setState/父组件更新等操作),这时候组件会被标记的dirty,会触发以下生命周期
-
componentWillReceiveProps
只会在父组件更新的时候,对应传给子组件的props变化时触发,如果在该周期更改了手动更改了props,最后会影响到虚拟dom的结构 -
shouldComponentUpdate
该方法用于判断组件是否需要更新,默认情况为true,如果返回false,将直接进入componentDidUpdate阶段 -
componentWillUpdate
更新之前调用,这里不能随便执行this.setState方法,一旦执行,将会导致死循环(除非能在shouldComponentUpdate很好的判断是否更新),后面分析代码的时候详细介绍 -
更新操作
实际执行了组件自己的(updateComponent) 方法 -
componentDidUpdate
组件更新后执行方法,同上,不能随便执行this.setState方法
在组件真正更新的时候,不要随便使用this.state方法,一是避免不必要的死循环,二是在这期间,state还没有更新到最新值,可能处理成错误的数据
销毁阶段
-
componentWillUnMount
组件在卸载前会执行该生命周期,可以用与回收定时器,回收特殊对象等作用
生命周期 in code
在以前的文章中已经在组件的各个阶段简单介绍过生命周期,这次我们针对不同周期,看看react中具体的调用实现
声明期
getDefaultProps
在createClass的时候就会调用一次,也就是说 xxx extends React.Component 会进行调用
function createClass(spec) {
var Constructor = identity(function(props, context, updater) {
// This constructor gets overridden by mocks. The argument is used
// by mocks to assert on what gets mounted.
if (this.__reactAutoBindPairs.length) {
bindAutoBindMethods(this);
}
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
this.state = null;
// ReactClasses doesn't have constructors. Instead, they use the
// getInitialState and componentWillMount methods for initialization.
var initialState = this.getInitialState ? this.getInitialState() : null;
this.state = initialState;
});
// 继承ReactClassComponent
Constructor.prototype = new ReactClassComponent();
Constructor.prototype.constructor = Constructor;
Constructor.prototype.__reactAutoBindPairs = [];
injectedMixins.forEach(mixSpecIntoComponent.bind(null, Constructor));
mixSpecIntoComponent(Constructor, IsMountedPreMixin);
mixSpecIntoComponent(Constructor, spec);
mixSpecIntoComponent(Constructor, IsMountedPostMixin);
//如果class中定义了getDefaultProps方法。调用getDefaultProps获取默认的props
if (Constructor.getDefaultProps) {
Constructor.defaultProps = Constructor.getDefaultProps();
}
// 对外暴露的方法,如render,shouldComponentUpdate等,如果在组件中没有实现,则返回null
// 暴露出来的所有方法可以在factory中变量ReactClassInterface看到
for (var methodName in ReactClassInterface) {
if (!Constructor.prototype[methodName]) {
Constructor.prototype[methodName] = null;
}
}
return Constructor;
}
我们看到当创建一个reactClass的时候,我们会声明一个Constructor function ,随后会执行getDefaultProps
方法
注意
这个生命周期只存在与ES5的写法中,在ES6中,我们往往使用XXComponent.defaultProps = {somepros}来声明,同理,defaultProps是由自己声明的,也只会执行一次
挂载期
constructor/getInitialState
这个方法在创建组件实例的时候会被调用,即new Component时,主要存在与React.creatClass()的Constructor函数中,同上面源码相同
componentWillMount/componentDidMount
在前文react源码阅读笔记(2)中提到,组件挂载的时候最终会调用组件的mountComponent方法,根据不同的组件类型(自定义组件,标签组件等)会实现不同的方法
自定义组件的mountComponent方法
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 a
var doConstruct = shouldConstruct(Component);
// 本质上就是new了一个React.createClass(), 即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);
inst = new StatelessComponent(Component);
this._compositeType = CompositeTypes.StatelessFunctional;
} else {
if (isPureComponent(Component)) {
this._compositeType = CompositeTypes.PureClass;
} else {
this._compositeType = CompositeTypes.ImpureClass;
}
}
// 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;
}
this._pendingStateQueue = null;
this._pendingReplaceState = false;
this._pendingForceUpdate = false;
var markup;
if (inst.unstable_handleError) {
markup = this.performInitialMountWithErrorHandling(
renderedElement,
hostParent,
hostContainerInfo,
transaction,
context,
);
} else {
// 在这执行了componentWillMount
markup = this.performInitialMount(
renderedElement, //前面代码中有 renderedElement = inst,即ReactComponent实体
hostParent,
hostContainerInfo,
transaction,
context,
);
}
if (inst.componentDidMount) { //生命周期componentDidMount
transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
}
return markup;
},
这段代码相对比较简单mountComponent调用_constructComponent
初始化,然后调用performInitialMount初始化挂载,在这函数中调用componentWillMount方法,挂载完成后调用componentDidMount
生存期
当组件已经被渲染出来的时候,我们只要通过调用this.setState()来更新组件,我们在react源码阅读笔记(3)batchedUpdates与Transaction中介绍过,更新组件的时候,最终会调用组件的updateComponent来完成更新,我们来看看自定义组件的updateComponent方法
updateComponent
updateComponent: function(
transaction,
prevParentElement,
nextParentElement,
prevUnmaskedContext,
nextUnmaskedContext,
) {
var inst = this._instance;
var willReceive = false;
var nextContext;
// 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, 通过上面代码判断willReceive,通过this.setState()执行到此时的时候,当前组件willReceive为true
if (willReceive && inst.componentWillReceiveProps) {
inst.componentWillReceiveProps(nextProps, nextContext);
}
// 合并state
var nextState = this._processPendingState(nextProps, nextContext);
var shouldUpdate = true;
if (!this._pendingForceUpdate) {
//生命周期shouldComponentUpdate
if (inst.shouldComponentUpdate) {
// 获取是否需要更新
shouldUpdate = inst.shouldComponentUpdate(
nextProps,
nextState,
nextContext,
);
} else {
if (this._compositeType === CompositeTypes.PureClass) {
shouldUpdate =
!shallowEqual(prevProps, nextProps) ||
!shallowEqual(inst.state, nextState);
}
}
}
this._updateBatchNumber = null;
if (shouldUpdate) {
this._pendingForceUpdate = false;
// Will set `this.props`, `this.state` and `this.context`.
// 执行更新,重新渲染
this._performComponentUpdate(
nextParentElement,
nextProps,
nextState,
nextContext,
transaction,
nextUnmaskedContext,
);
} else {
// If it's determined that a component should not update, we still want
// to set props and state but we shortcut the rest of the update.
// 不需要重新渲染,但是会把state props context设置为最新值
this._currentElement = nextParentElement;
this._context = nextUnmaskedContext;
inst.props = nextProps;
inst.state = nextState;
inst.context = nextContext;
}
},
在调用updateComponent中,通过对比ParentElement,来判断是否需要调用componentWillReceiveProps方法,随后,主动调用组件的shouldComponentUpdate方法进行判断是否需要渲染,如果组件没有该方法,则默认需要渲染,在写react组件的时候,重写该方法可以进行优化(不过官方不建议这么做,官方建议是,在出现性能瓶颈的时候才需要对代码进行优化)
然后,来分析一下重新渲染所调用的** _performComponentUpdate **方法
_performComponentUpdate
_performComponentUpdate: function(
nextElement,
nextProps,
nextState,
nextContext,
transaction,
unmaskedContext,
) {
var inst = this._instance;
// 判断是否存在componentDidUpdate方法
var hasComponentDidUpdate = Boolean(inst.componentDidUpdate);
var prevProps;
var prevState;
var prevContext;
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;
inst.props = nextProps;
inst.state = nextState;
inst.context = nextContext;
this._updateRenderedComponent(transaction, unmaskedContext);
if (hasComponentDidUpdate) {
transaction
.getReactMountReady()
.enqueue(
inst.componentDidUpdate.bind(
inst,
prevProps,
prevState,
prevContext,
),
inst,
);
}
},
在这个方法中,先判断了组件是否有实现componentDidUpdate方法,随后依次调用了 _updateRenderedComponent方法以及 transaction.getReactMountReady().enqueue方法
_updateRenderedComponent
_updateRenderedComponent: function(transaction, context) {
var prevComponentInstance = this._renderedComponent;
var prevRenderedElement = prevComponentInstance._currentElement;
// 获取渲染的ReactElement
var nextRenderedElement = this._renderValidatedComponent();
var debugID = 0;
// 判断是否做DOM diff
if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) {
// 递归updateComponent,更新子组件的Virtual DOM
ReactReconciler.receiveComponent(
prevComponentInstance,
nextRenderedElement,
transaction,
this._processChildContext(context),
);
} else {
// 不做DOM diff,则先卸载掉,然后再加载。也就是先unMountComponent,再mountComponent
var oldHostNode = ReactReconciler.getHostNode(prevComponentInstance);
ReactReconciler.unmountComponent(prevComponentInstance, false);
var nodeType = ReactNodeTypes.getType(nextRenderedElement);
this._renderedNodeType = nodeType;
var child = this._instantiateReactComponent(
nextRenderedElement,
nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */,
);
this._renderedComponent = child;
var nextMarkup = ReactReconciler.mountComponent(
child,
transaction,
this._hostParent,
this._hostContainerInfo,
this._processChildContext(context),
debugID,
);
//渲染
this._replaceNodeWithMarkup(
oldHostNode,
nextMarkup,
prevComponentInstance,
);
}
},
同mountComponent一样,这里也是通过递归的方式将子组件一一更新,这里不在过多的介绍了
transaction.getReactMountReady().enqueue
看到enqueue方法,就会让我们想起 CallbackQueue中的enqueue,在react源码阅读笔记(4)Pool与CallbackQueue我们介绍过,enqueue会把方法压入调用队列中,当执行notifyAll方法的时候,会将调用队列里面的函数依次执行。那么这里enqueue的函数mountComponent会在什么时候调用呢?答案就在transaction中,由react源码阅读笔记(3)batchedUpdates与Transaction知道,transaction在声明和结束分别会调用initialize和close方法,也就是说,notifyAll很有可能是在某个wrapper中的close调用的,这时候我们看看这个wrapper就一目了然了
var UPDATE_QUEUEING = {
initialize: function() {
// 重置回调队列
this.callbackQueue.reset();
},
close: function() {
// 执行回调方法
this.callbackQueue.notifyAll();
},
};
var TRANSACTION_WRAPPERS = [NESTED_UPDATES, UPDATE_QUEUEING];
销毁阶段
当组件被unmount的时候,react会触发componentWillUnmount函数,在这个函数中,多用于去销毁一些绑定的额外事件,定时器等任务
unmountComponent
unmountComponent: function(safely) {
if (!this._renderedComponent) {
return;
}
var inst = this._instance;
// 是否存在componentWillUnmount方法
if (inst.componentWillUnmount && !inst._calledComponentWillUnmount) {
inst._calledComponentWillUnmount = true;
if (safely) {
var name = this.getName() + '.componentWillUnmount()';
ReactErrorUtils.invokeGuardedCallback(
name,
inst.componentWillUnmount.bind(inst),
);
} else {
//执行componentWillUnmount
inst.componentWillUnmount();
}
}
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 = 0;
this._topLevelWrapper = null;
//从instance map中去除该对象
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;
},
},
卸载组件的代码比起更新和挂载就简单很多了,他首先调用了组件自己的componentWillUnmount方法,随后进行递归,分别调用子组件的unmountComponent,最后将内部变量全部置空
总结
深入的了解了React的生命周期,让我们清楚到了一个React组件由声明到最后销毁都依次做了些什么内容,也能更好的让我们对React组件进行优化与改进,同时,组件体会到了React设计者设计transaction, callbackQueue的巧妙与强大!