一个前端,爱跑步、爱吉他、爱做饭、爱生活、爱编程、爱南芳姑娘,爱我所爱。世间最温暖又无价的是阳光、空气与爱,愿它们能带你去更远的地方。

  • 文章
  • 心情
  • 照片墙
  • 留言板
  • 工具
  • 友链
  • biaoblog

    专注web开发技术分享

    setState()批处理

    技术 173 2021-06-30 09:22

    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)
    }
    

    此篇文章转载于:https://blog.csdn.net/fesfsefgs/article/details/108023095


    文章评论

    评论列表(0