1. 初步了解 React 生命周期
React 生命周期可以分为挂载、更新、卸载三个阶段。主要可以分为两类:
组件挂载和卸载;
组件接收新的数据和状态时的更新;
1.1 组件的挂载
组件的挂载是最基本过程,这个过程主要做初始化。在这初始化个过程中 componentWillMount 会在 render 方法之前执行,而 componentDidMount 方法会在 render 方法之后执行。分别代表了渲染前后时刻。写一个简单的例子:
class Demo extends React.Component {
static propTypes = {}
static defaultProps = {}
constructor(props) {
super(props)
this.state = {}
}
componentWillMount() {}
render() {return null}
componentDidMount() {}
}
如上,这个初始化过程没有什么特别之处,这里包括读取初始 state、读取初始 props、以及两个生命周期方法 componentWillMount 和 componentDidMount。这些都只会在组件初始化时执行一次。
1.2 组件的卸载
组件的卸载只有 componentWillUnmount 这个一个方法。
1.3 组件的更新
组件的更新发生在父组件传递 props 或者自身执行 setState 改变状态这一系列操作的情况下。和组件更新的生命周期方法有以下几个:
class Demo extends React.Component {
// 当组件更新时会顺序执行以下方法
componentWillReceiveProps(nextProps){} //
shouldComponentUpdate(nextProps, nextState) {} // 返回 false 则停止向下执行,默认返回 true
componentWillUpdate(nextProps, nextState) {}
render() {}
componentDidUpdate(prevProps, prevState) {}
}
tip: shouldComponentUpdate 可以用来正确的渲染组件的。理想情况下,父级节点改变时,只会重新渲染一条链路上和该 props 相关的组件。但是默认情况下,React 会渲染所有的节点,因为 shouldComponentUpdate 默认返回 true。
2. 深入了解 React 生命周期
前面大致介绍了组件的生命周期主要分为三种状态:挂载、更新、卸载。如下图可以详细了解不同状态的执行顺序:
使用 ES6 classes 构建组件的时候 static defaultProps={} 其实就是调用内部的 getDefaultProps 方法。constructor 中的 this.state={} 其实就是调用内部的 getInitialState 方法。
2.1 详解 React 生命周期
自定义组件生命周期通过 3 个阶段进行控制:MOUNTING,RECEIVE_PROPS,UNMOUNTING, 它负责通知组件当时所处的阶段,应该执行生命周期中的哪个步骤。这三个阶段分别对应三个方法:
2.2 使用 createClass 创建自定义组件
createClass 是创建自定义组件的入口方法,负责管理生命周期中的 getDefaultProps 方法。该方法在整个生命周期中只执行一次,这样所有实例初始化的 props 都能共享。通过 createClass 创建自定义组件,利用原型继承 ReactClassComponent 父类,按顺序合并 mixin,设置初始化 defaultProps,返回构造函数。
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;
//ReactClasses 没有构造函数,通过 getInitialState 和 componentWillMount 来代替
var initialState = this.getInitialState ? this.getInitialState() : null;
this.state = initialState;
};
// 原型继承 ReactClassComponent 父类
Constructor.prototype = new ReactClassComponent();
Constructor.prototype.constructor = Constructor;
Constructor.prototype.__reactAutoBindPairs = [];
// 合并 mixin
injectedMixins.forEach(
mixSpecIntoComponent.bind(null, Constructor)
);
mixSpecIntoComponent(Constructor, spec);
// 所有 mixin 合并后初始化 defaultProps(在生个生命周期中,defaultProps 只执行一次)
if (Constructor.getDefaultProps) {
Constructor.defaultProps = Constructor.getDefaultProps();
}
// 设置原型
for (var methodName in ReactClassInterface) {
if (!Constructor.prototype[methodName]) {
Constructor.prototype[methodName] = null;
}
}
// 最后返回的是构造函数
return Constructor;
},
}
2.3 阶段一:MOUNTING
mountComponent 负责管理生命周期中的 getInitialState,componentWillMount,render 和 componentDidMount。由于 getDefaultProps 是在初始化构造函数中进行管理的,所以也是整个生命周期中最先执行的。而且只执行一次也可以理解了。
由于通过 ReactCompositeComponentBase 返回的是一个虚拟节点,所以需要通过 instantiate-ReactComponent 去得到实例,在通过 mountComponent 拿到结果作为当前自定义元素的结果。
通过 mountComponent 挂载组件,初始化序号,标记参数等,判断是否为无状态组件,并进行对应的初始化操作,比如初始化 props,context 等参数。利用 getInitialState 获取初始化 state, 初始化更新队列和更新状态。
如果存在 componentWillMount 则执行,如果此时在 componetWillMount 调用 setState 方法,是不会触发 re-render 方法,而是会进行 state 合并,且 inst.state = this._processPendingState(inst.props, inst.context) 在 componentWillMount 之后执行。因此在 render 中才可以获取到最新的 state。
因此,React 是通过更新队列 this._pendingStateQueue 以及更新状态 this._pendingReeplaceState 和 this._pendingForUpdate 来实现 setState 的异步更新。
当渲染完成后,若存在 componentDidMount 则调用。
其实 mountComponent 是通过递归渲染内容。由于递归的特性,父组件的 componentWillMount 在其子组件的 componentWillMount 之前调用, 父组件的 componentDidMount 在其子组件的 componentDidMount 之后调用。
//react/src/renderers/shared/reconciler/ReactCompositeComponent.js
// 当组件挂载时,会分配一个递增编号,表示执行 ReactUpdates 时更新组件的顺序
var nextMountID = 1
var ReactCompositeComponentMixin = {
// 初始化组件,渲染标记,注册事件监听器
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;
var renderedElement;
// 这里判断是否是无状态组件,无状态组件没有更新状态序列,只关注更新
if (Component.prototype && Component.prototype.isReactComponent) {
inst = new Component(publicProps, publicContext, ReactUpdateQueue);
} else {
inst = Component(publicProps, publicContext, ReactUpdateQueue);
if (inst == null || inst.render == null) {
renderedElement = inst;
warnIfInvalidElement(Component, renderedElement);
invariant(
inst === null ||
inst === false ||
ReactElement.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);
}
}
// 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 = ReactUpdateQueue;
this._instance = inst;
// 将实例存储为一个引用
ReactInstanceMap.set(inst, this);
// 初始化 state
var initialState = inst.state;
if (initialState === undefined) {
inst.state = initialState = null;
}
// 初始化 state 更新队列
this._pendingStateQueue = null;
this._pendingReplaceState = false;
this._pendingForceUpdate = false;
var markup;
// 如果挂载错误则执行 performInitialMountWithErrorHandling(方法如下)
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) {
// Roll back to checkpoint, handle error (which may add items to the transaction), and take a new checkpoint
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
);
// 递归渲染
var markup = ReactReconciler.mountComponent(
this._renderedComponent,
transaction,
nativeParent,
nativeContainerInfo,
this._processChildContext(context)
);
return markup;
}
}
2.4 阶段二:REVEIVE_PROPS
updateComponent 负责管理生命周期的 componentWillReceiveProps、shouldComponent、componentWillUpdate、render、componentDidUpdate。
首先通过 updateComponent 更新组件,如果前后元素不一致,说明需要组件更新。
若存在 componentWillReceiveProps,则执行。如果此时在 componentWillReceiveProps 中调用 setState 是不会触发 re-render,而是会进行 state 合并。且在 componentWillReceiveProps,shouldComponentUpate 和 componentWillUpdate 是无法获取更新后的 this.state。需要设置 inst.state = nextState 后才可以。因此只有在 render 和 componentDidUpdate 中才可以获取更新后的 state.
调用 shouldComponentUpdate 判断是否需要进行组件更新,如果存在 componentWillUpdate 则执行。
updateComponet 也是通过递归渲染的,由于递归的特性,父组件的 componentWillUpdate 在子组件之前执行,父组件的 componentDidUpdate 在其子组件之后执行。
2.5 阶段三:UNMOUNTING
unmountComponent 负责管理 componentWillUnmount。在这个阶段会清空一切。
// 组件卸载
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);
},