好久没写东西了。。。。
来头条之后, 每天身体被掏空。也没多少时间写东西,
今天周末,上午没啥事, 简单写写,
一个例子
假如有这样一个点击执行累加场景:
// ...
this.state = {
count: 0,
}
incrementCount() {
this.setState({count: this.state.count + 1});
}
handleIncrement = () => {
this.incrementCount();
this.incrementCount();
this.incrementCount();
}
<button ref="button" onClick={this.handleIncrement}>点击</button>
// ...
每一次点击, 会执行三次累加,看一下输入:
并没有达到预期的效果,纠正也很简单:
incrementCount() {
this.setState((prevState) => {
return {count: prevState.count + 1}
});
}
再看输出:
setState 的时候, 一个传入了object, 一个传入了更新函数。
区别在于: 传入一个更新函数,就可以访问当前状态值。 setState调用是批量处理的,因此可以让更新建立在彼此之上,避免冲突。
那问题来了, 为什么前一种方式就不行呢? 具体一点说, setState为什么不会同步更新组件状态呢? 带着这个疑问,继续往下看。
进入这个问题之前,我们先回顾一下现在对setState的认知:
- setState不会立刻改变React组件中state的值.
- setState通过触发一次组件的更新来引发重绘.
重绘指的就是引起React的更新生命周期函数4个函数:
- shouldComponentUpdate(被调用时this.state没有更新;如果返回了false,生命周期被中断,虽然不调用之后的函数了,但是state仍然会被更新)
- componentWillUpdate(被调用时this.state没有更新)
- render(被调用时this.state得到更新)
- componentDidUpdate
- 多次setState函数调用产生的效果会合并。
如果每一次setState调用都走一圈生命周期,光是想一想也会觉得会带来性能的问题,其实这四个函数都是纯函数,性能应该还好,但是render函数返回的结果会拿去做Virtual DOM比较和更新DOM树,这个就比较费时间。
目前React会将setState的效果放在队列中,积攒着一次引发更新过程,为的就是把Virtual DOM和DOM树操作降到最小,用于提高性能。
查阅一些资料后发现,某些操作还是可以同步更新this.state的。
直接说结论吧:
在React中,如果是由React引发的事件处理(比如通过onClick引发的事件处理),调用setState不会同步更新this.state,除此之外的setState调用会同步执行this.state。
所谓“除此之外”,指的是绕过React通过addEventListener
直接添加的事件处理函数,还有通过setTimeout/setInterval
产生的异步调用。
https://jsbin.com/mavolejufi/edit?html,js,console,output
盗用一张图:
在React的setState函数实现中,会根据一个变量isBatchingUpdates判断是直接更新this.state还是放到队列中。
而isBatchingUpdates默认是false,也就表示setState会同步更新this.state,但是有一个函数batchedUpdates,这个函数会把isBatchingUpdates修改为true,而当React在调用事件处理函数之前就会调用这个batchedUpdates,造成的后果,就是由React控制的事件处理过程setState不会同步更新this.state。
知道上面的一些理论之后, 我们再看一个例子就很清晰了:
class Example extends React.Component {
constructor() {
super();
this.state = {
val: 0
};
}
componentDidMount() {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 1 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 2 次 log
setTimeout(() => {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 3 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 4 次 log
}, 0);
}
render() {
return null;
}
};
毫无疑问输出 0 0 2 3 ;
前两次在isBatchingUpdates 中,不用更新state, 输出两个0。
后面两次会同步更新, 分别输出2, 3;
上面的例子,我们就知道了setState 是可以同步更新的,但是还是尽量避免直接使用, 仅作了解用。
如果你非要玩一些骚操作,写出这样的代码去直接去操作this.state:
this.state.count = this.state.count + 1;
this.state.count = this.state.count + 1;
this.state.count = this.state.count + 1;
this.setState();
我只能说, 大胸弟, 你很骚。吾有旧友叼似汝,而今坟草丈许高。
根据以上内容, 简单重复下结论:
- 不要直接去操作this.state, 这样会造成不必要的性能更问题和隐患。
- 由React引发的事件处理,调用setState不会同步更新this.state,除此之外的setState调用会同步执行this.state。
就简单写到这吧, 要出门了 :)。
扩展阅读:
https://reactjs.org/docs/faq-state.html#what-does-setstate-do
关于事务,还有相关源码的问题可以参考:
https://zhuanlan.zhihu.com/p/20328570
https://zhuanlan.zhihu.com/p/25882602
https://zhuanlan.zhihu.com/p/26069727
https://zhuanlan.zhihu.com/p/25990883
https://reactjs.org/docs/faq-state.html#what-does-setstate-do
https://reactjs.org/docs/react-component.html#setstate