setState()批处理,合并策略,控制批处理----batchUpdates

之前一直在做vue开发,可能对vue了解的多一些,也期待vue3的发展,但是近期想开始整明白react背后的事情

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的更新策略



setState 批量更新

  • 除了virtual-dom的优化减少数据更新的频率是另外一种手段,也就是React的批量更新
  • 顾名思义,批量更新,可以避免短期内的多次渲染,攒为一次性更新。

setState()合并策略:

我对攒为一次性更新的理解是:是覆盖而不是叠加(类似于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实现

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逻辑存在于哪里?

除了事件处理函数会执行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)
}

猜你喜欢

转载自blog.csdn.net/fesfsefgs/article/details/108023095