今天和大家聊一聊React如何实现批量状态更新。
引子
我们知道React的setState方法并不是同步执行的。
在React的生命周期中发生的多次setState的变更会进行合并,最终减少推送给浏览器的DOM变更次数,从而提升前端性能。
那么这部分到底是怎么实现的呢?我们来看下
Transaction
在React执行点击事件或者生命周期函数时,会使用一个Transaction对象将整个执行过程包裹成一个事务。
Transaction对象结构如下图所示
<pre>
* wrappers (injected at creation time)
* + +
* | |
* +-----------------|--------|--------------+
* | v | |
* | +---------------+ | |
* | +--| wrapper1 |---|----+ |
* | | +---------------+ v | |
* | | +-------------+ | |
* | | +----| wrapper2 |--------+ |
* | | | +-------------+ | | |
* | | | | | |
* | v v v v | wrapper
* | +---+ +---+ +---------+ +---+ +---+ | invariants
* perform(anyMethod) | | | | | | | | | | | | maintained
* +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | +---+ +---+ +---------+ +---+ +---+ |
* | initialize close |
* +-----------------------------------------+
* </pre>
在Transcation对象中整个执行效果有些类似我们平时做的单元测试。
分为初始化,执行和清理。
这样就可以在我们所有生命周期函数和点击事件中的setState方法调用前设置一个环境——isBatchingUpdates。
这个环境有什么作用呢?
实际上,所有的setState会将生成一个update对象,并加入到一个队列中。
接着会调用下面的requestWork方法,进行更新的任务调度。
而在其中,会判断isBatchingUpdates是否为true。
如果是,则将任务放置在队列,等待UnbatchingUpdates时统一执行
否则,就会同步执行。
最新源码
如果你现在去拉react的最新代码,会发现里面已经找不到Transcation这个类了。
不过你可以在ReactFiberWorkLoop文件中,找到batchedEventUpdates这个方法。
里面的实现基本是和transcation一样的,只是bool值换成了枚举
存在问题
可能聪明的小伙伴已经看出了这个地方存在的问题。
就是如果setState不在这个transcation过程中执行,那么就会导致同步更新。
也就是说如果要实现transcation的效果,必须setState在同步方法中调用。
比如说通过setTimeout方法,异步设置state。
此时setState在执行requestWork时,会发现已经处于Transcation之外了,isBatchingUpdates就会是false。
从而每次setState都会导致render,降低性能。
有兴趣的同学,可以在这个代码示例中,看到这里在普通事件和promise的回调中,setState导致的render次数不同。
解决方案
那有没有办法对这类的setState调用也进行批量执行呢?
有的。
第一种方式是,将所有的更新放到一个setState中。比如
setState({
a:1,
b:2
});
这样只会触发一次更新。
第二种方式是通过ReactDOM.unstable_batchedUpdates方法,这样
ReactDOM.unstable_batchedUpdates(() => {
setState({a:1});
setState({b:2});
});
这里最终会调用到React的batchedUpdates方法,也是类似Transcation的方式,将执行包裹在一个BactedContext的环境中。从而实现批量调用。
参考文档:
- javascript - How to synchronously render components with React (or render to string) - Stack Overflow
- Simplifying state management in React apps with batched updates - LogRocket Blog
- React事务的一些理解 - 个人文章 - SegmentFault 思否
- React源码解析(三):详解事务与更新队列 - 掘金
- React 源码学习(四):事务机制 - 知乎
- React事务机制解析_javascript_小敏哥的专栏-CSDN博客
- react事务 - 简书
- React中的Transaction - 传不习乎
- React Transaction - Que’s Blog
-
[React-函数batchedUpdates和Transaction执行 Axiu Blog](https://axiu.me/coding/react-batchedupdates-and-transaction/)
本文会经常更新,请阅读原文: https://xinyuehtx.github.io/post/%E7%90%86%E8%A7%A3React%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E6%89%B9%E9%87%8F%E7%8A%B6%E6%80%81%E6%9B%B4%E6%96%B0.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名黄腾霄(包含链接: https://xinyuehtx.github.io ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 。