这篇文章是对之前的一片文章react生命周期整理提到的生命周期的解析,本文会从源码的角度,对react的各个生命周期的实现原理进行不太详细的分析
getDefaultProps与getInitialState
首先我们来看一下以下两个代码片段,第一个是用es5创建组件的方法,需要调用react的createClasss方法,第二个是es6创建组件的方法,定义一个类继承react.component
var Greeting = React.createClass({
getDefaultProps: function() {
return {
name: '小敏哥' //默认属性值
};
},
getInitialState: function() {
return {count: this.props.initialCount}; //初始化state
},
handleClick: function() {
this.setState({count:this.state.count++})
},
render: function() {
return <h1>Hello, {this.props.name},{this.state.count}</h1>;
}
});
module.exports = Greeting;
class Greeting extends React.Component {
constructor(props) {
super(props);
this.state = {count: props.initialCount};
this.handleClick = this.handleClick.bind(this);
}
static defaultProps = {
name: '小敏哥'
};
handleClick() {
this.setState({count:this.state.count++})
}
render() {
return <h1>Hello, {this.props.name},{this.state.count}</h1>;
}
}
export default Greating;
上面的两段代码其实做的是同一件事:声明了一个自定义组件,贴出这两段代码,是为了从es5和es6的角度解释getDefaultProps和getInitialState这两个生命周期方法。
我们先看es6的方法,显然更优雅并且更容易理解,其实就是声明一个Component的子类,我们注意到,defaultProps是一个静态属性,而this.state在contructor中进行初始化,由于defaultProps是一个静态属性,所以它只会初始化一次,而state的初始化则在每次实例化的时候,都会通过constructor方法进行初始化,事实上,通过es6的写法,通过用静态defaultProps和在constructor方法中初始化state,已经不需要getDefaultProps和getInitialState这两个生命周期,但我们仍然需要知道,它做的是同一件事。
接着我们看es5的方法,它定义了getDefaultProps和getInitialState这两个生命周期方法,从这种写法看上去,其实我们看不出什么时候会执行这两个声明周期方法,这个时候,我们来看一下react的源码
createClass: function(spec) {
var Constructor = function(props, context, updater) {
//这里面是实例化时需要做的东西
// This constructor gets overridden by mocks. The argument is used
// by mocks to assert on what gets mounted.
// Wire up auto-binding
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;
invariant(
typeof initialState === 'object' && !Array.isArray(initialState),
'%s.getInitialState(): must return an object or null',
Constructor.displayName || 'ReactCompositeComponent'
);
this.state = initialState;
};
Constructor.prototype = new ReactClassComponent();
Constructor.prototype.constructor = Constructor;
Constructor.prototype.__reactAutoBindPairs = [];
injectedMixins.forEach(
mixSpecIntoComponent.bind(null, Constructor)
);
mixSpecIntoComponent(Constructor, spec);
// Initialize the defaultProps property after all mixins have been merged.
if (Constructor.getDefaultProps) {
Constructor.defaultProps = Constructor.getDefaultProps();
}
invariant(
Constructor.prototype.render,
'createClass(...): Class specification must implement a `render` method.'
);
// Reduce time spent doing lookups by setting these on the prototype.
for (var methodName in ReactClassInterface) {
if (!Constructor.prototype[methodName]) {
Constructor.prototype[methodName] = null;
}
}
return Constructor;
},
为了理解方便,我删除了一小部分不影响理解的代码,我们可以注意到这个createClass就是一个构造函数,它返回了一个Constructor,实例化组件的时候,我们就是去new这个Constructor来生成组件,这里我们注意到这句代码
if (Constructor.getDefaultProps) {
Constructor.defaultProps = Constructor.getDefaultProps();
}
我们可以发现,这里做的,就是es6中静态属性defaultProps做的事,只不过通过getDefaultProps这个方法把数据给传进来,并且将其执行结果赋值给Constructor,这样一来,getDefaultProps这个方法只会执行一次在es5的写法中也就完全解释得通了,因为无论你如何去new createClass返回的方法去生成对象,这个方法已经在构造函数里面执行了,而初始state则通过一下两句代码进行初始化
var initialState = this.getInitialState ? this.getInitialState() : null;
this.state = initialState;
但我们可以看到,这两句代码是在Constructor中执行的,也就是说实在实例化过程中才会执行getInitialState,每次实例化的时候,getInitialState返回的值,将会作为组件的默认state
这样一来,通过对代码的分析,我们对这两个生命周期的理解也就大体清晰了,getDefaultProps(或者说默认props初始化,es6写法不存在这个方法)只会执行一次,而getInitialState(或者说默认state初始化,es6写法也不存在这个方法)则会在每次实例化的时候执行。(此处放个小括号并且加斜体吐槽一下《深入react技术栈》中的表述,里面是这样写的:当使用 ES6 classes 编写 React 组件时,class MyComponent extends React.Component 其实就是调用内部方法 createClass 创建组件。经过上面的分析之后,发现这不是扯淡么,两种不同的写法,es6写法根本就不会去调用createClass方法,如有不同意见可在评论区留言指教,谢谢)
componentWillMount,render和componentDidMount
将这三个放到一起写的原因在于,因为他们都是在第一次渲染的时候执行(主要还是因为源码离得近),当组件首次渲染时,会调用mountComponent方法,源码如下所示,其中,主要的渲染操作是在performInitialMount中进行的
mountComponent: function (
transaction,
nativeParent,
nativeContainerInfo,
context
) {
this._context = context;
this._mountOrder = nextMountID++;
var publicProps = this._processProps(this._currentElement.props);
var publicContext = this._processContext(context);
var Component = this._currentElement.type;
// Initialize the public class
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);
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;
// 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;
}
//开始执行渲染操作
var markup;
markup = this.performInitialMount(renderedElement, nativeParent, nativeContainerInfo, transaction, context);
//渲染完成,执行componentDidMount
if (inst.componentDidMount) {
transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
}
return markup;
}
performInitialMount: function(renderedElement, nativeParent, nativeContainerInfo, transaction, context) {
var inst = this._instance;
//判断是否有componentWillMount,有则执行
if (inst.componentWillMount) {
inst.componentWillMount();
// When mounting, calls to `setState` by `componentWillMount` will set
// `this._pendingStateQueue` without triggering a re-render.
if (this._pendingStateQueue) {
inst.state = this._processPendingState(inst.props, inst.context);
}
}
// If not a stateless component, we now render
if (renderedElement === undefined) {
renderedElement = this._renderValidatedComponent();
}
this._renderedNodeType = ReactNodeTypes.getType(renderedElement);
this._renderedComponent = this._instantiateReactComponent(
renderedElement
);
//开始递归渲染
var markup = ReactReconciler.mountComponent(
this._renderedComponent,
transaction,
nativeParent,
nativeContainerInfo,
this._processChildContext(context)
);
return markup;
},
长长的一大段代码,同样为了便于理解,删除了一部分代码,其实也就是想说明一点,componentWillMount定义之后会在渲染之前被调用,componentWillMount被调用之后,会进行state的合并操作,而componentDidMount则会在渲染完成之后被调用,render函数则会在渲染的过程中被调用。此处需要特别注意一行代码:
if (this._pendingStateQueue) {
inst.state = this._processPendingState(inst.props, inst.context);
}
这两行代码的作用是用于合并当前state,如果我们在componentWillMount中执行setState方法的话,是不会触发react的重新渲染的,只会将当前的state合并到缓存中,等到整个渲染事务完结之后,react才会对整个state进行重新进行渲染,这主要是基于性能的考虑,毕竟如果每次setState都重新渲染,将会非常影响性能,所以react对于setState有一套基于事务的操作,但是由于setState基于事务的渲染机制比较复杂,可以重新开一篇文章进行解析了,此处就不再赘述,这里只需要知道,这行代码是用于合并state,并且它在componentWillMount之后执行,所以我们在componentWillMount中获取到的state,并不是最新的,而且即使在componentWillMount中进行setState,也不会触发重新渲染,如果要获取到最新的state,则需等到生命周期到达render或者componentDidMount中,获取的state才会是准确的。
componentWillReceiveProps,shouldComponentUpdate,componentWillUpdate,componentDidUpdate
updateComponent: function(
transaction,
prevParentElement,
nextParentElement,
prevUnmaskedContext,
nextUnmaskedContext
) {
var inst = this._instance;
var willReceive = false;
var nextContext;
var nextProps;
// 检查context是否改变
if (this._context === nextUnmaskedContext) {
nextContext = inst.context;
} else {
nextContext = this._processContext(nextUnmaskedContext);
willReceive = true;
}
//检测props是否发生变化
// Distinguish between a props update versus a simple state update
if (prevParentElement === nextParentElement) {
// Skip checking prop types again -- we don't read inst.props to avoid
// warning for DOM component props in this upgrade
nextProps = nextParentElement.props;
} else {
nextProps = this._processProps(nextParentElement.props);
willReceive = true;
}
if (willReceive && inst.componentWillReceiveProps) {
inst.componentWillReceiveProps(nextProps, nextContext);
}
//合并state,但仅获得新的state,并未赋值
var nextState = this._processPendingState(nextProps, nextContext);
//判断当前是否允许更新
var shouldUpdate =
this._pendingForceUpdate ||
!inst.shouldComponentUpdate ||
inst.shouldComponentUpdate(nextProps, nextState, nextContext);
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.
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;
if (hasComponentDidUpdate) {
prevProps = inst.props;
prevState = inst.state;
prevContext = inst.context;
}
//执行钩子函数
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);
//渲染完成后调用componentDidUpdate
if (hasComponentDidUpdate) {
transaction.getReactMountReady().enqueue(
inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext),
inst
);
}
},
又是洋洋洒洒一百多行源码,大概写清楚当props或者state发生变化时,这几个钩子函数时怎么执行的,首先通过标记willReceive变量,当context或者props发生变化时,如果存在componentWillReceiveProps,则执componentWillReceiveProps,如果仅仅只是state发生变化,则会跳过,接着执行state合并,然后会执行shouldComponentUpdate,获得该函数的返回值,用于判断渲染是否需要往下进行,如果不往下进行,则仅保留数据,并不进行渲染,如果进行渲染,则会执行_performComponentUpdate方法进行具体的渲染,之后执行componentWillUpdate方法,这是在渲染之前的最后一个钩子函数,然后我们注意到执行componentWillUpdate之后,下面的这句代码
inst.props = nextProps;
inst.state = nextState;
inst.context = nextContext;
在这之后才出现props,state,context的更新,那么说明,在这之前,我们在componentWillReceiveProps,shouldComponentUpdate,componentWillUpdate这几个钩子函数中获取到的props,state,context都将不会是最新的,同样,由于setState的事务尚未完成,在componentWillReceiveProps中进行setState操作仅仅会合并当前state并在事务完成后一起进行渲染。最后,当所有的渲染操作完成之后,如果定义了componentDidUpdate,则会调用componentDidUpdate函数
至于componentWillUnmount,大体上就是在组件卸载时会被调用,具体将会unmountComponent函数,因为实现比较简单,此处就不再贴出来了,有兴趣的读者可以自行查看源码,毕竟,源码贴的有点多了(源码比文字多系列)