内容简介:在React官方文档中有这么一句话React does not guarantee that the state changes are applied immediately。在我最开始使用React的时候,我只是简单的把这句话当做React这个框架的约束,但是随着使用的深入,setState这个函数也让我觉得越来越神秘。在这篇文章中,我将通过反思自己在使用react中遇到的关于setState的一些问题,深入react源码,分析setState这个函数。在React官方文档中有这么一句话下面这两个很经典
在React官方文档中有这么一句话React does not guarantee that the state changes are applied immediately。在我最开始使用React的时候,我只是简单的把这句话当做React这个框架的约束,但是随着使用的深入,setState这个函数也让我觉得越来越神秘。在这篇文章中,我将通过反思自己在使用react中遇到的关于setState的一些问题,深入react源码,分析setState这个函数。
以下代码全部基于React15(React16代码太复杂了看不懂哇- -)。
setState不一定是同步的
在React官方文档中有这么一句话 state-updates-may-be-asynchronous 。
下面这两个很经典也是新人很容易糊涂的场景就是由上面这句模棱两可的话带来的。
class Demo extends Component { state = { count: 1 } onClickHandler = () => { this.setState({count: this.state.count + 1}); console.log(this.state.count); // console.log 结果 1 this.setState({count: this.state.count + 1}); console.log(this.state.count); // console.log 结果 1 } render() { const { count } = this.state; return ( <button onClick={this.onClickHandler}>{count}</button> ); } } class SetTimeoutDemo extends Component { state = { count: 1 } onClickSetTimeoutHandler = () => { setTimeout(() => { this.setState({count: this.state.count + 1}); console.log(this.state.count); // console.log 结果 2 this.setState({count: this.state.count + 1}); console.log(this.state.count); // console.log 结果 3 }, 0); } render() { const { count } = this.state; return ( <button onClick={this.onClickSetTimeoutHandler}>{count}</button> ); } }
stackBlitz的demo在这 Clicker 和 SetTimeoutClicker
上面的两个结果非常令人疑惑,在我刚刚接触React的时候,我并不是特别理解为什么这种写法会产生这样的差异,只是简单的相信这是React的一种Magic。
setState的内部实现
但是所有的Magic其实都有踪可循,经过一番调查,我大致理清楚了setState内部实现的调用关系。
setState的内部调用栈如上图所示,略去一些细枝末节的代码之后,简化为如下的流程图。
在这个流程图中,有几个非常重要的地方需要关注
- 所有的
setState
时更新的state都以partial state的形式进入一个队列中,等待在batchUpdate中进行一次更新 - batch update有一个
isBatchingUpdate
的锁,当正在进行batching update时,无法再次触发batching update,当前component被push到dirtyComponent
数组中等待后续更新
以事务(transaction)的方式进行更新
在batch update中,React使用事务机制进行更新,事务机制的运行原理如下
/** * <pre> * wrappers (injected at creation time) * + + * | | * +-----------------|--------|--------------+ * | v | | * | +---------------+ | | * | +--| wrapper1 |---|----+ | * | | +---------------+ v | | * | | +-------------+ | | * | | +----| wrapper2 |--------+ | * | | | +-------------+ | | | * | | | | | | * | v v v v | wrapper * | +---+ +---+ +---------+ +---+ +---+ | invariants * perform(anyMethod) | | | | | | | | | | | | maintained * +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|--------> * | | | | | | | | | | | | * | | | | | | | | | | | | * | | | | | | | | | | | | * | +---+ +---+ +---------+ +---+ +---+ | * | initialize close | * +-----------------------------------------+ * </pre> * */
事务会在创造时注入多个wrapper,每个wrapper是一个有着 initialize
和 close
两个函数的对象。当执行 perform(anyMethod)
的时候,调用顺序依次为
/** wrapper1.initialize -> wrapper2.initialize -> anymethod -> wrapper1.close -> wrapper2.close */
batch update中的事务
React实现了两个Wrapper用作batch update,在接入事务后,batch update的流程如下
React在一次事务中完成 batch update锁的打开和关闭,来保证batch update的进行。
再回头看看我们之前的问题
回到我们
在解释过 setState
内部的工作原理之后,其实对于上面这种奇怪的输出已经不难理解了。React在事件触发时就已经 处在了一个大的事务之中 , isBatchingUpdate
被置成了 true
,随后的setState在调用时会进入 dirtyComponent
队列,在下一次batch update中进行更新。所以在下一次batch update之前, this.state
都不会得到更新。所以事实上调用结果如下。
//this.state.count = 1 this.setState({count: this.state.count + 1}); // 等于this.setState({count: 2}) this.setState({count: this.state.count + 1}); // 等于this.setState({count: 2})
而如果 setState
函数进行了 setTimeout
的包裹,由于 EventLoop 的特点,会保证 setState
一定是在前一条message之后,也就是上一次batch update完之后进行执行, isBatchingUpdate
为 false=,此时的 =setState
会直接触发一次完整的batch update,保证 this.state
被同步更新。而下一次再进行 setTimeout
包裹的 setState
操作原理同上。
因为 相同的原因 ,在组件生命周期中调用 setState
方法也会和事件触发类似, setState
并不会跟预期中的一样进行同步更新。
还有什么方式可以同步更新state?
在上面的例子中,我们提到,在使用React封装的事件时会进入一个事务,使得 isBatchingUpdate
为 true
。 而当我们使用原生的事件机制时(比如 addEventListener
),由于缺少了React的封装,会使得 setState
直接触发 batch update更新,从而同步更新state。
class RawDemo extends Component { constructor() { super(); this.state = { count: 1 }; } componentDidMount() { document.querySelector('#foo').addEventListener('click', () => { this.setState({count: this.state.count + 1}); console.log(this.state.count); this.setState({count: this.state.count + 1}); console.log(this.state.count); }) } render() { return ( <button id="foo">{this.state.count}</button> ); } }
总结
- React中的batch update使用事务进行完成
- React通过
isBatchingUpdate
来控制是否更新组件,当isBatchingUpdate
为true
时,组件会被推入dirtyComponent
数组中而不会即时更新 - 普通情况下之所以
setState
表现为非同步,原因是在React封装的事件绑定(或者在生命周期)中调用setState
处于一个大的事务中,isBatchingUpdate
已经被置为true
- 除了通过
setTimout
, 还可以通过原生的事件绑定机制来同步更新state(并不推荐使用)。
参考资料
以上所述就是小编给大家介绍的《Deep In React(五)setState中的黑魔法》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 资料 | Git 魔法.pdf
- 【译】canvas笔触魔法师
- (Angular)模版引用变量的魔法
- iOS黑魔法 - Method Swizzling
- 跟着 WWDC 一起探秘符号解析的魔法
- 魔法书2:测试Arduino 执行速度极限
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。