理想是火,点燃熄灭的灯。
react开发对setState的使用可能一点也不陌生,但肯定会碰到过这种情况:
import React from 'react' export default class BatchedDemo extends React.Component { state = { number: 0, } handleClick = () => { this.countNumber() } countNumber() { this.setState({ number: this.state.number + 1 }) this.setState({ number: this.state.number + 1 }) this.setState({ number: this.state.number + 1 }) } render() { return <button id="myButton" onClick={this.handleClick}>Num: {this.state.number}</button> } }
点击button按钮后,发现只加了1,why?
这就涉及到了setState的更新策略
除了virtual-dom的优化,减少数据更新的频率是另外一种手段,也就是React的批量更新。
顾名思义,批量更新,可以避免短期内的多次渲染,攒为一次性更新。
我对攒为一次性更新的理解是:是覆盖而不是叠加(类似于Object.assign())
证明如下: 把countNumber函数改为如下,如果是覆盖,那么只会执行
number: this.state.number + 5,相当于把前面同类都覆盖了 countNumber() { this.setState({ number: this.state.number + 11 }) this.setState({ number: this.state.number + 20 }) this.setState({ number: this.state.number + 5, }) }
点击按钮后:
所以如下操作:
this.setState({ age: 18 }) this.setState({ color: 'black‘ }) this.setState({ age: 20 }) this.setState({ name: 'yank' })
会被React合成为一次setState调用
this.setState({ name: 'yank', age: 20, color: 'black' })
而我们要搞清楚的就是setState()到底是如何去合并的,我能自由控制它的合并吗?
通过伪代码更好的去理解setState()是如何去合并的
setState实现
setState(newState) { if (this.canMerge) { this.updateQueue.push(newState) return } // 下面是真正的更新: dom-diff, lifeCycle... ... }
然后countNumber()方法调用之后,把隐式操作通过伪代码显示出来:
countNumber() { this.canMerge = true this.setState({ number: this.state.number + 11 }) this.setState({ number: this.state.number + 20 }) this.setState({ number: this.state.number + 5, }) this.canMerge = false // 通过this.updateQueue合并出finalState const finalState = ... // 此时canMerge 已经为false 故而走入时机更新逻辑 this.setState(finaleState) }
可以看出 setState首先会判断是否可以合并,如果可以合并this.canMerge = true
,就直接返回了。直到this.canMerge = false时,代表finalState已经合并完成,就开始走更新,需要注意的是这些都是react内部的隐式操作,是发生在React内部的,React对它们有完全的控制权。
除了事件处理函数会执行canMerge逻辑,在执行componentDidMount前后也会有canMerge逻辑,可以理解为:React委托代理了所有的事件,在执行你的函数/componentDidMount之前,会执行React逻辑,这样React也是有时机执行canMerge逻辑的。
如何控制canMerge逻辑
批量更新是极好滴!我们当然希望任何setState都可以被批量,关键点在于React是否有时机执行canMerge逻辑,也就是React对目标函数有没有控制权。如果没有控制权,那么就不会执行canMerge逻辑,也就不会发生setState()被react隐式合并了
通过setTimeout脱离react的控制
import React from 'react' export default class BatchedDemo extends React.Component { state = { number: 0, } handleClick = () => { this.setState({ number: this.state.number + 1 }) this.setState({ number: this.state.number + 2 }) this.setState({ number: this.state.number + 3 }) setTimeout(() => { this.setState({ number: this.state.number + 4 }) this.setState({ number: this.state.number + 5 }) this.setState({ number: this.state.number + 6 }) }) } render() { return <button id="myButton" onClick={this.handleClick}>Num: {this.state.number} </button> } }
分析上述代码:
handleClick 是事件回调,React有时机执行canMerge逻辑,所以x为+1,+2,+3是合并的,handleClick结束之后canMerge被重新设置为false。注意这里有一个setTimeout(fn, 0)。 这个fn会在handleClick之后调用,而React对setTimeout并没有控制权,React无法在setTimeout前后执行canMerge逻辑,所以x为4,5,6是无法合并的,所以fn这里会存在3次dom-diff。React没有控制权的情况有很多: Promise.then(fn), fetch回调,xhr网络回调等等。
所以点击按钮: 3+4+5+6=18
通过unstable_batchedUpdates重回react的控制
以上代码的setTimeout中,我想让react去拿回控制权,合并代码,怎么办呢?
需要用unstable_batchedUpdates这个API
代码如下:
import React from 'react' import { unstable_batchedUpdates as batchedUpdates } from 'react-dom' export default class BatchedDemo extends React.Component { state = { number: 0, } handleClick = () => { this.setState({ number: this.state.number + 1 }) this.setState({ number: this.state.number + 2 }) this.setState({ number: this.state.number + 3 }) setTimeout(() => { //通过这个api,让react拿回控制权,执行canMerge逻辑 batchedUpdates(() => { this.setState({ number: this.state.number + 4 }) this.setState({ number: this.state.number + 5 }) this.setState({ number: this.state.number + 6 }) }) }) } render() { return <button id="myButton" onClick={this.handleClick}>Num: {this.state.number} </button> } }
打印如下:3+6=9
最后看一下这个api的伪代码:
function unstable_batchedUpdates(fn) { this.canMerge = true fn() this.canMerge = false const finalState = ... //通过this.updateQueue合并出finalState this.setState(finaleState) }
此篇文章转载于:https://blog.csdn.net/fesfsefgs/article/details/108023095
作者: Bill 本文地址: http://biaoblog.cn/info?id=1625016158478
版权声明: 本文为原创文章,版权归 biaoblog 个人博客 所有,欢迎分享本文,转载请保留出处,谢谢!