以下几个问题是我们在实际开发中经常会遇到的场景,下面用几个简单的示例代码来还原一下。
setState
现在有两个组件
componentDidMount() {
console.log('parent componentDidMount');
render() {
return (
<SetState2></SetState2>
<SetState></SetState>
}
组件内部放入同样的代码,并在
Setstate1
中的
componentDidMount
中放入一段同步延时代码,打印延时时间:
componentWillUpdate() {
console.log('componentWillUpdate');
componentDidUpdate() {
console.log('componentDidUpdate');
componentDidMount() {
console.log('SetState调用setState');
this.setState({
index: this.state.index + 1
console.log('state', this.state.index);
console.log('SetState调用setState');
this.setState({
index: this.state.index + 1
console.log('state', this.state.index);
}
下面是执行结果:
说明:
setState
不会立即更新
didmount
后,父组件
didmount
,然后执行更新
setstate
?
在
setTimeout
中调用
setState
(例子和在浏览器原生事件以及接口回调中执行效果相同)
componentDidMount() {
setTimeout(() => {
console.log('调用setState');
this.setState({
index: this.state.index + 1
console.log('state', this.state.index);
console.log('调用setState');
this.setState({
index: this.state.index + 1
console.log('state', this.state.index);
}, 0);
}
执行结果:
说明:
didmount
后执行
setState
同步更新
setState
只有一次生效?
分别执行以下代码:
componentDidMount() {
this.setState({ index: this.state.index + 1 }, () => {
console.log(this.state.index);
this.setState({ index: this.state.index + 1 }, () => {
console.log(this.state.index);
}
componentDidMount() {
this.setState((preState) => ({ index: preState.index + 1 }), () => {
console.log(this.state.index);
this.setState(preState => ({ index: preState.index + 1 }), () => {
console.log(this.state.index);
}
执行结果:
1
1
2
2
说明:
setstate
会被合并成一次
state
不会被合并
由于源码比较复杂,就不贴在这里了,有兴趣的可以去
github
上
clone
一份然后按照下面的流程图去走一遍。
partialState
:
setState
传入的第一个参数,对象或函数
_pendingStateQueue
:当前组件等待执行更新的
state
队列
isBatchingUpdates
:react用于标识当前是否处于批量更新状态,所有组件公用
dirtyComponent
:当前所有处于待更新状态的组件队列
transcation
:react的事务机制,在被事务调用的方法外包装n个
waper
对象,并一次执行:
waper.init
、被调用方法、
waper.close
FLUSH_BATCHED_UPDATES
:用于执行更新的
waper
,只有一个
close
方法
对照上面流程图的文字说明,大概可分为以下几步:
partialState
参数存储在当前组件实例的state暂存队列中。
waper
方法,遍历待更新组件队列依次执行更新。
componentWillReceiveProps
。
state
进行合并,获得最终要更新的state对象,并将队列置为空。
componentShouldUpdate
,根据返回值判断是否要继续更新。
componentWillUpdate
。
render
。
componentDidUpdate
。
在
react
的生命周期和合成事件中,
react
仍然处于他的更新机制中,这时
isBranchUpdate
为true。
按照上述过程,这时无论调用多少次
setState
,都会不会执行更新,而是将要更新的
state
存入
_pendingStateQueue
,将要更新的组件存入
dirtyComponent
。
当上一次更新机制执行完毕,以生命周期为例,所有组件,即最顶层组件
didmount
后会将
isBranchUpdate
设置为false。这时将执行之前累积的
setState
。
由执行机制看,
setState
本身并不是异步的,而是如果在调用
setState
时,如果
react
正处于更新过程,当前更新会被暂存,等上一次更新执行后在执行,这个过程给人一种异步的假象。
在生命周期,根据JS的异步机制,会将异步函数先暂存,等所有同步代码执行完毕后在执行,这时上一次更新过程已经执行完毕,
isBranchUpdate
被设置为false,根据上面的流程,这时再调用
setState
即可立即执行更新,拿到更新结果。
partialState
合并机制
我们看下流程中
_processPendingState
的代码,这个函数是用来合并
state
暂存队列的,最后返回一个合并后的
state
。
_processPendingState: function (props, context) {
var inst = this._instance;
var queue = this._pendingStateQueue;
var replace = this._pendingReplaceState;
this._pendingReplaceState = false;
this._pendingStateQueue = null;
if (!queue) {
return inst.state;
if (replace && queue.length === 1) {
return queue[0];
var nextState = _assign({}, replace ? queue[0] : inst.state);
for (var i = replace ? 1 : 0; i < queue.length; i++) {
var partial = queue[i];
_assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);