• React中this.setState方法原理解析(详解)


    摘要

    我们知道,在React中没有像Vue那种数据双向绑定的效果。而this.setState方法就是用来对数据进行更改的。而通过this.setState方法更改的数据,会让组件的render重新渲染,并且刷新数据。

    而这一篇文章,主要是简单的实现一下this.setState方法,为了实现该方法,就要知道this.setState方法具有什么特点。

    首先在React组件中,我们先定义一个state和setState方法:

      myState = {
        value: 0
      }
      
      mySetState = ( changeState ) =>{
      
        this.setState(
          this.myState
        )
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    这里可能会说,为什么在自己写的mySetState 方法里还要调用React的setState呢?都调用人家的了还算自己写的吗?

    由于在React中,render只能处理通过setState方法修改的值,所以这里我们在mySetState 中调用了一下。但是mySetState方法的具体实现还是我们自己去完成。

    1.异步的setState

    首先,我们看一段代码:

      state = {
        value: 0
      }
    
    • 1
    • 2
    • 3
        setTimeout(() => {
          console.log('SetTimeOut ---- '+this.state.value);
        }, 0);
        new Promise((resolve,reject) => {
          resolve(this.state.value)
        })
        .then(res => {
          console.log('Promise ---- '+res);
        })
    
        this.setState({
          value: this.state.value+1
        })
    
        console.log(this.state.value);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    这段代码会输出什么呢?
    在这里插入图片描述
    由这个结果我们可以知道上面的代码执行顺序:


    (1)console.log(this.state.value);

    (2)Promise代码

    (3)setState方法

    (4)setTimeOut方法

    所以setState一定是一个异步的方法。

    在实现的时候,要注意异步的问题。

    2.多个setState方法

    我们继续看一段代码:

        this.setState({
          value: this.state.value+1
        })
        this.setState({
          value: this.state.value+1
        })
        this.setState({
          value: this.state.value+1
        })
        this.setState({
          value: this.state.value+1
        })
        this.setState({
          value: this.state.value+1
        })
    
        setTimeout(() => {
          console.log(this.state.value);
        }, 0);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    这段代码输出的会是1还是5呢?

    答案是1,这是React为了效率所作的一个优化。防止每次setState都会导致render重新渲染!

    而如果我就想要这个效果,React也是提供了一个解决办法,就是setState方法可以接受一个函数作为参数:

        this.setState( (preState) => {
          return {
            value: preState.value+1
          }
        } )
    
        this.setState( (preState) => {
          return {
            value: preState.value+1
          }
        } )
    
        this.setState( (preState) => {
          return {
            value: preState.value+1
          }
        } )
    
        this.setState( (preState) => {
          return {
            value: preState.value+1
          }
        } )
    
        setTimeout(() => {
          console.log(this.state.value);
        }, 0);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    preState代表的是上一个state。

    3.手动实现mySetState

    OK,有了上面对setState方法分了解,我们可以手动的实现一下mySetState方法

    首先解决就是调用多个setState方法的时候,所以我们不能每次调用mySetState方法都让state值更新,也就是这么写:

      mySetState = ( changeState ) =>{
    
        Object.assign(this.myState,changeState)
    
        this.setState(
          this.myState
        )
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    我们只需要知道最后一个changeState,所以这里我们维护一个队列,用来保存所有的changeState,然后每次调用mySetState 方法的时候,将changeState放到队列里面

    
      queue = []
      
      mySetState = ( changeState ) =>{
    
        Promise.resolve().then(()=>{
          this.stateShift()
        })
    
        this.queue.push(changeState)
    
        this.setState(
          this.myState
        )
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    重头戏来了,我们已经有了这个队列,那我们是如何让这个队列的changeState 出来的呢?

    这里我们写一个方法:

      stateShift() {
        let empty;
        while(empty = this.queue.shift()){
          
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    通过迭代器的方式,遍历队列。empty就是拿到的每个changeState 。changeState 有两种形式,对象的时候肯定是很好写的。

      stateShift() {
        let empty;
        while(empty = this.queue.shift()){
          if(typeof empty === 'object'){
            Object.assign(this.myState,empty)
          }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    只需要让新出来的去替换旧的就可以了。如果changeState 是一个方法,这个时候,我们需要手动去维护一个preState变量,这个变量的作用就是用来保存上一个state

    所以具体的实现应该为:

      stateShift() {
        let empty;
        while(empty = this.queue.shift()){
          if(!this.preState){
            this.preState =  Object.assign({},this.myState)
          }
          if(typeof empty === 'object'){
            Object.assign(this.myState,empty)
          }else if(typeof empty === 'function'){
            Object.assign(this.myState,empty(this.preState))
          }
    
          this.preState = this.myState
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    最后一步,这个方法应该什么时候调用。其实需要注意的无非就是,要在所有的changeState 都放到队列中,再进行调用。

    而this.queue.push(changeState)这段代码又是同步的代码,所以在它之前,通过异步的方式调用,就可以实现出这种效果

      mySetState = ( changeState ) =>{
    
        Promise.resolve().then(()=>{
          this.stateShift()
        })
    
        this.queue.push(changeState)
    
        this.setState(
          this.myState
        )
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    最后将整个实现代码附上:

    import React, { Component } from 'react'
    
    export default class Child extends Component {
    
      state = {
        value: 0
      }
    
      myState = {
        value: 0
      }
    
      queue = []
    
      mySetState = ( changeState ) =>{
    
        Promise.resolve().then(()=>{
          this.stateShift()
        })
    
        this.queue.push(changeState)
    
        this.setState(
          this.myState
        )
      }
    
    
      stateShift() {
        let empty;
        while(empty = this.queue.shift()){
          if(!this.preState){
            this.preState =  Object.assign({},this.myState)
          }
          if(typeof empty === 'object'){
            Object.assign(this.myState,empty)
          }else if(typeof empty === 'function'){
            Object.assign(this.myState,empty(this.preState))
          }
    
          this.preState = this.myState
        }
      }
    
      add = ()=>{
        //测试代码
        // this.mySetState( (pre) => {
        //   return {
        //     value: pre.value + 1
        //   }
        // } )
        // this.mySetState( (pre) => {
        //   return {
        //     value: pre.value + 1
        //   }
        // } )
        // this.mySetState( (pre) => {
        //   return {
        //     value: pre.value + 1
        //   }
        // } )
    
        // this.mySetState({
        //   value: this.myState.value+1
        // })
        // this.mySetState({
        //   value: this.myState.value+1
        // })
        // this.mySetState({
        //   value: this.myState.value+1
        // })
        // this.mySetState({
        //   value: this.myState.value+1
        // })
    
        setTimeout(() => {
          console.log('SetTimeOut ---- '+this.myState.value);
        }, 0);
        new Promise((resolve,reject) => {
          resolve(this.myState.value)
        })
        .then(res => {
          console.log('Promise ---- '+res);
        })
    
        this.mySetState({
          value: this.myState.value+1
        })
    
        console.log(this.myState.value);
    
    
      }
    
      render() {
    
        return (
          <div>
            <span>{this.myState.value}</span>
            <br></br>
            <button onClick={this.add}>++</button>
          </div>
        )
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105

    最后值得注意的是,这里只是针对于setState的一些特点去模拟了一下setState的实现,具体的源码可能不会像这样简单!

  • 相关阅读:
    【全网最全】2023华为杯研究生数学建模B题完整思路+python代码+20页超详细启发式算法+FFT(后续会更新)
    Python少儿编程小课堂(六)入门篇(6)
    车载通信架构 —— DDS协议介绍
    dropbear-ssh2
    web网页设计期末课程大作业:美食餐饮文化主题网站设计——中华美德6页面HTML+CSS+JavaScript
    开源数据库postgresql在统信系统上的离线安装
    dds:publish
    Centos安装Redis
    P07 dom4j 解析技术
    bugxxx
  • 原文地址:https://blog.csdn.net/weixin_46726346/article/details/126610535