1、异步更新机制
我们知道,this.state是只读的,更新状态不能直接修改,而是通过this.setState方法。这是为什么呢?this.state只是一个对象,我们修改它的值是没有意义的。仔细想一想,我们之所以要修改state,无非是为了改变页面的渲染状态;所以React设计setState方法就是为了重新渲染页面。
我们可以在setState之后打印一下this.state的值,会发现它并没有改变,还是之前的值。如果我们需要在短时间内多次setState,并且每次setState的值跟之前的状态有关,我们就需要使用函数作为setState的参数了,这个函数参数接收两个参数(当前的state和当前的props)。举个例子:
// 最终产生的结果是this.state.value只增加了1
function test1() {
this.setState({ value: this.state.value + 1 });
this.setState({ value: this.state.value + 1 });
this.setState({ value: this.state.value + 1 });
}
多次setState会合并
前面我们了解到setState并不会立即改变state的值,而是将其放到一个任务队列里,最终将多个setState合并,一次性更新页面。所以我们可以在代码里多次调用setState,每次只需要关注当前修改的字段即可。
另外,需要注意的是,setState触发页面重新渲染需要经过以下生命周期:
shouldComponentUpdate(){}
componentWillUpdate(){}
render(){
// 更新state值
}
componentDidUpdate(){}
// 换种写法,结果就如我们所意了
function test2() {
this.setState((state, props) => ({ value: state.value + 1 }));
this.setState((state, props) => ({ value: state.value + 1 }));
this.setState((state, props) => ({ value: state.value + 1 }));
}
要注意的是,在以上的setState中,this.state并没有被改变,依然,要等到render函数被重新执行时(或者shouldComponentUpdate函数返回false之后)才被改变。
因为使用函数式setState,React会保证每次调用函数时,state都已经合并了之前的状态修改结果。
setState还有第二个参数callback,所以下面这种写法也是可以的:
this.setState({ value: this.state.value + 1 }, (val) => {
this.setState({ value: this.state.value + 1 }, () => {
this.setState({ value: this.state.vavlue + 1 });
});
});
2、同步更新机制
在React的setState函数实现中,会根据一个变量isBatchingUpdates判断是直接更新this.state还是放到队列中回头再说,而isBatchingUpdates默认是false,也就表示setState会同步更新this.state,但是,有一个函数batchedUpdates,这个函数会把isBatchingUpdates修改为true,而当React在调用事件处理函数之前就会调用这个batchedUpdates,造成的后果,就是由React控制的事件处理过程setState不会同步更新this.state。
componentDidMount() {
// 同步跟新
document.querySelector('#btn-raw').addEventListener('click', this.onClick);
}
onClickLater() {
// 同步更新
setTimeout(() => {
this.onClick();
});
}
onClick() {
// 异步
this.setState({count: this.state.count + 1});
console.log('# this.state', this.state);
}
render() {
console.log('#enter render');
return (
<div>
<div>{this.state.count}
<button onClick={this.onClick}>Increment</button>
<button id="btn-raw">Increment Raw</button>
<button onClick={this.onClickLater}>Increment Later</button>
</div>
</div>
)
}
同步更新this.state的话,每一次调用setState都会引发同步的更新过程,这会更新过程很频繁,也就会导致性能问题。
3、异步更新设计原因
- 保证内部状态一致性
- 性能优化
- 更多的可能行
详情参见:https://juejin.im/post/5a6f440a51882573336652af
在React中,如果是由React引发的事件处理(比如通过onClick引发的事件处理),调用setState不会同步更新this.state,除此之外的setState调用会同步执行this.state。所谓“除此之外”,指的是绕过React通过addEventListener直接添加的事件处理函数,还有通过setTimeout/setInterval产生的异步调用。
4、总结
setState
只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout
中都是同步的。setState
的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。