react setState 原理

  • 组件的数据来源有两个地方,分别是属性对象和状态对象
  • 属性是父组件传递过来的,不可更改
  • 状态是自己内部的,改变状态的唯一方式就是setState
  • 属性和状态的变化都会引起视图更新
import React from "react";
import ReactDOM from "react-dom";

/**
 * 属性是由父组件传递过来的,不能改变
 * 状态是组件内部,由自己维护,外界无法访问  改变状态的唯一方式就是setState
 */
class Counter extends React.Component{
  constructor(props) { //构造函数是唯一给状态赋值的地方
    super(props)
    //定义状态的地方
    this.state = {number: 0}
  }
  render(){
    //当我们调用setState的时候会引起状态的改变和组件的更新
    console.log('render')
    return (
      <div>
        <p>{this.state.number}</p>
        <button onClick={() => this.setState({number: this.state.number + 1})}>+</button>
      </div>
    )
  }
}
// let counter = new Counter()
ReactDOM.render(<Counter></Counter>,document.getElementById('root'))

构造函数是唯一定义状态并且赋值的地方,当我们要改变状态的值的时候需要通过setState方法,而不是直接修改state的值,并且每次调用setState的时候会引起状态的改变和组件的更新。

1.不能直接修改state的值

import React from "react";
import ReactDOM from "react-dom";

/**
 * 属性是由父组件传递过来的,不能改变
 * 状态是组件内部,由自己维护,外界无法访问  改变状态的唯一方式就是setState
 */
class Counter extends React.Component{
  constructor(props) { //构造函数是唯一给状态赋值的地方
    super(props)
    //定义状态的地方
    this.state = {number: 0}
  }
  add = () => {
    this.state.number += 1
  }
  render(){
    //当我们调用setState的时候会引起状态的改变和组件的更新
    console.log('render')
    return (
      <div>
        <p>{this.state.number}</p>
        <button onClick={this.add}>+</button>
      </div>
    )
  }
}
// let counter = new Counter()
ReactDOM.render(<Counter></Counter>,document.getElementById('root'))

像这种直接修改state值得方法并不会生效。

 2.state的更新可能是异步

我们知道调用setState会触发更新操作,这个过程包括更新state,创建新的VNode,在经过diff算法对比差异,决定需要渲染那一部分,假如他是同步更新的话,每次调用都要执行一次前面的流程,这样会造成很大的性能问题,所以需要将多个setState放进一个队列里面,、然后再一个一个执行,最后再一次性更新视图,这样会提高性能。举个例子:

let state = {number: 0}
function setState(newState) {
    state = newState
    console.log(state)
}
setState({number: state.number + 1})
setState({number: state.number + 2})
setState({number: state.number + 3})

这段代码会通过 setState 方法改变state值,我们看看打印结果:

 可以看到每次调用setState都会改变state的值并且进行渲染,这将是一个非常消耗性能的问题。

所以React针对setState做了一些特别的优化:将多个setState的调用放进了一个队列,合并成一个来执行,这意味着当调用setState时,state并不会立即更新,看下面这个例子:

let state = {number: 0}
let updataQueue = []
function setState(newState) {
    updataQueue.push(newState)
}
setState({number: state.number + 1})
setState({number: state.number + 2})
setState({number: state.number + 3})

updataQueue.forEach(item =>{
    state = item
})
console.log(state) // 3

我们预想的是结果等于6.但是输出的却是3,这是因为变成异步更新之后state的值并不会立即更新,所以每次拿到的state都是 0 ,如果我们想要让结果等于 6,也就是每次都能拿到最新值,那就需要给setState()传递一个函数作为参数,在这个函数中可以拿到每次改变后的值,并通过这个函数的返回值得到下一个状态。

let state = { number: 0 }
let updataQueue = [] //更新函数队列
let callbackQueue = [] //回调函数队列
function setState(updataState,callback) {
    //入队
    updataQueue.push(updataState)
    callbackQueue.push(callback)
    
}
//清空队列
function flushUpdata () {
    for(let i = 0; i < updataQueue.length; i++) {
        state = updataQueue[i](state) //拿到每次改变后的值作为下一个的状态
    }
    state = state
    callbackQueue.forEach(callbackItem => callbackItem())
}

function add(){
    setState(preState => ({ number: preState.number + 1}),() => {
        console.log(state)
   })
   setState(preState => ({ number: preState.number + 2}),() => {
       console.log(state)
   })
   setState(preState => ({ number: preState.number + 3}),() => {
       console.log(state)
   })
   //批量更新
   flushUpdata()
}

add()
console.log(state) // 6

 

 由于回调函数也是异步执行的,所以最后一次性输出的都是6.

 改写成class类的形式如下:

class Component {
    constructor() {
        this.state = {
            number: 0
        }
        this.batchUpdata = false
        this.updataQueue = [] //更新队列
        this.callbackQueue = [] //回调函数队列
    }
    setState(updataState, callback) {
        if (this.batchUpdata) {
            this.updataQueue.push(updataState) //放入队列
            this.callbackQueue.push(callback)
        }
    }
    flushUpdata() {
        let state = this.state
        // this.updataQueue.forEach(newStateitem => this.state = newStateitem)
        for(let i = 0; i < this.updataQueue.length; i++) {
            state = this.updataQueue[i](state)
        }
        this.state = state
        this.callbackQueue.forEach(callback => callback())
    }
    add() {
        this.batchUpdata = true //开启合并模式

        this.setState(preState => ({ number: preState.number + 1}),() => {
            console.log(this.state)
       })
       this.setState(preState => ({ number: preState.number + 2}),() => {
           console.log(this.state)
       })
       this.setState(preState => ({ number: preState.number + 3}),() => {
           console.log(this.state)
       })

        //批量更新
        this.flushUpdata()
    }
}
let c = new Component()
c.add()
console.log(c.state)

现在这个逻辑对于setState传入的参数是函数很适合,但是有时候我们希望传入的是对象,且希望利用setState执行完之后做一些操作,比如在请求到数据之后隐藏进度条等,这个时候就需要setState能变为同步执行,这个时候我们会借助promise、setTimeout等方法来改变setState让它变为同步的。也就是不用放入队列,而是立即执行,但是以上逻辑不支持同步的情况,我们需要修改:

class Component {
    constructor() {
        this.state = {
            number: 0
        }
        this.batchUpdata = false
        this.updataQueue = [] //更新队列
        this.callbackQueue = [] //回调函数队列
    }
    setState(updataState, callback) {
        if (this.batchUpdata) { //批量更新
            this.updataQueue.push(updataState) //放入队列
            this.callbackQueue.push(callback)
        }else { //直接更新
            console.log('直接更新')
            //如果是函数需要把老值传进去
            if(typeof updataState === 'function') {
                this.state = updataState(this.state)
            }else {
                this.state = updataState
            }
        }
    }
    flushUpdata() {
        let state = this.state
        // console.log(this.updataQueue)
        for(let i = 0; i < this.updataQueue.length; i++) {
            //为了兼容参数为函数和对象的情况需要判断一下 参数为对象的时候不用传上一个的状态值,参数为函数的时候需要传上一个的状态给下一个状态
            if(typeof this.updataQueue[i] === 'function') {
                state = this.updataQueue[i](state)
            }else {
                state = this.updataQueue[i]
            }
        }
        this.state = state
        this.callbackQueue.forEach(callback => {
            if(callback) callback() //为了兼容参数为函数和对象的情况需要判断一下,参数为对象的时候没有回调函数就不执行
        })
        this.batchUpdata = false //更新完毕置为false
    }
    add() {
        this.batchUpdata = true //开启合并模式
        //不会放进更新队列
       setTimeout(() => {
        this.setState({number: this.state.number + 4})
        console.log(this.state)
       },1000)
        this.setState({number: this.state.number + 1})
    //     this.setState(preState => ({ number: preState.number + 1}),() => {
    //         console.log(this.state)
    //    })
    //    this.setState({number: this.state.number + 1})

        //批量更新
        this.flushUpdata()
    }
}
let c = new Component()
c.add()
console.log('end'+ JSON.stringify(c.state))

批量处理机制就是为了减少setState刷新页面的次数,setTimeout,promise等异步方法可以直接跳过批量处理机制,setState调几次就改几次。

 https://www.cnblogs.com/jiuyi/p/9263114.html这篇文章对于同步更新讲的比较好

3.seState的更新会被合并

当调用setState的时候,React会把你要修改的那一部分的对象合并到当前的state上面,举个栗子

class Counter extends React.Component{
  constructor(props) { //构造函数是唯一给状态赋值的地方
    super(props)
    this.add = this.add.bind(this)
    //定义状态的地方
    this.state = {name: 'leah' ,number: 0}
  }
  
  add (event) {
    console.log(event)
    // this.state.number += 1 不能直接修改state的值
    this.setState({number: this.state.number + 1})
  }
  render(){
    console.log(this)
    //当我们调用setState的时候会引起状态的改变和组件的更新
    console.log('render')
    return (
      <div>
        <p>{this.state.name}</p>
        <p>{this.state.number}</p>
        <button onClick={this.add}>+</button>
      </div>
    )
  }
}

当前我们只修改了state.number这个时候,name还是会渲染,我们需要对这部分进行合并

class Component {
    constructor() {
        this.state = {
            name: 'leah',
            number: 0
        }
        this.batchUpdata = false
        this.updataQueue = [] //更新队列
        this.callbackQueue = [] //回调函数队列
    }
    setState(updataState, callback) {
        if (this.batchUpdata) { //批量更新
            this.updataQueue.push(updataState) //放入队列
            this.callbackQueue.push(callback)
        }else { //直接更新
            console.log('直接更新')
            //如果是函数需要把老值传进去
            if(typeof updataState === 'function') {
                this.state = updataState(this.state)
            }else {
                this.state = updataState
            }
        }
    }
    flushUpdata() {
        let state = this.state
        // console.log(this.updataQueue)
        for(let i = 0; i < this.updataQueue.length; i++) {
            //为了兼容参数为函数和对象的情况需要判断一下 参数为对象的时候不用传上一个的状态值,参数为函数的时候需要传上一个的状态给下一个状态
            let partialState = typeof this.updataQueue[i] === 'function' ? this.updataQueue[i](this.state) : this.updataQueue[i]
            state = {...state, ...partialState}
        }
        this.state = state
        this.callbackQueue.forEach(callback => {
            if(callback) callback() //为了兼容参数为函数和对象的情况需要判断一下,参数为对象的时候没有回调函数就不执行
        })
        this.batchUpdata = false //更新完毕置为false
    }
    add() {
        this.batchUpdata = true //开启合并模式
        //不会放进更新队列
       setTimeout(() => {
        this.setState({number: this.state.number + 4})
        console.log(this.state)
       },1000)
        this.setState({number: this.state.number + 1})
    //     this.setState(preState => ({ number: preState.number + 1}),() => {
    //         console.log(this.state)
    //    })
    //    this.setState({number: this.state.number + 1})

        //批量更新
        this.flushUpdata()
    }
}
let c = new Component()
c.add()
console.log('end'+ JSON.stringify(c.state))

4.在组件实例中this的指向问题:

一般来说,类的方法里this是undefined,那如何让普通方法的this指向组件实例呢?

4.1.箭头函数

class Counter extends React.Component{
  constructor(props) { //构造函数是唯一给状态赋值的地方
    super(props)
    //定义状态的地方
    this.state = {number: 0}
  }
  add = (event) => {
    console.log(event)
    // this.state.number += 1 不能直接修改state的值
    this.setState({number: this.state.number + 1})
    this.setState({number: this.state.number + 2})
    this.setState({number: this.state.number + 3})
  }
  render(){
    console.log(this)
    //当我们调用setState的时候会引起状态的改变和组件的更新
    console.log('render')
    return (
      <div>
        <p>{this.state.number}</p>
        <button onClick={this.add}>+</button>
      </div>
    )
  }
}

4.2.匿名函数

class Counter extends React.Component{
  constructor(props) { //构造函数是唯一给状态赋值的地方
    super(props)
    //定义状态的地方
    this.state = {number: 0}
  }
  add (event) {
    console.log(event)
    // this.state.number += 1 不能直接修改state的值
    this.setState({number: this.state.number + 1})
    this.setState({number: this.state.number + 2})
    this.setState({number: this.state.number + 3})
  }
  render(){
    console.log(this)
    //当我们调用setState的时候会引起状态的改变和组件的更新
    console.log('render')
    return (
      <div>
        <p>{this.state.number}</p>
        <button onClick={() => this.add()}>+</button>
      </div>
    )
  }
}

4.3.bind绑定

class Counter extends React.Component{
  constructor(props) { //构造函数是唯一给状态赋值的地方
    super(props)
    //定义状态的地方
    this.state = {number: 0}
  }
  /**
   * 合成事件 react合成事件
   * 事件代理
   * event 并不是原始的dom对象 而是react二次封装的事件对象 可以复用
   */
  add (event) {
    console.log(event)
    // this.state.number += 1 不能直接修改state的值
    this.setState({number: this.state.number + 1})
    this.setState({number: this.state.number + 2})
    this.setState({number: this.state.number + 3})
  }
  render(){
    console.log(this)
    //当我们调用setState的时候会引起状态的改变和组件的更新
    console.log('render')
    return (
      <div>
        <p>{this.state.number}</p>
        <button onClick={this.add.bind(this)}>+</button>
      </div>
    )
  }
}

但是这样绑定有个问题,就是每次渲染的时候都需要绑定一次,所以可以在构造函数里面一次性绑定

class Counter extends React.Component{
  constructor(props) { //构造函数是唯一给状态赋值的地方
    super(props)
    this.add = this.add.bind(this)
    //定义状态的地方
    this.state = {number: 0}
  }
  /**
   * 合成事件 react合成事件
   * 事件代理
   * event 并不是原始的dom对象 而是react二次封装的事件对象 可以复用
   */
  add (event) {
    console.log(event)
    // this.state.number += 1 不能直接修改state的值
    this.setState({number: this.state.number + 1})
    this.setState({number: this.state.number + 2})
    this.setState({number: this.state.number + 3})
  }
  render(){
    console.log(this)
    //当我们调用setState的时候会引起状态的改变和组件的更新
    console.log('render')
    return (
      <div>
        <p>{this.state.number}</p>
        <button onClick={this.add}>+</button>
      </div>
    )
  }
}

5.合成事件

合成事件原理:利用事件冒泡机制
如果react事件绑定在了真实DOM节点上,一个节点同事有多个事件时,页面的响应和内存的占用会受到很大的影响。因此SyntheticEvent作为中间层出现了。
事件没有在目标对象上绑定,而是在document上监听所支持的所有事件,当事件发生并冒泡至document时,react将事件内容封装并叫由真正的处理函数运行。

 

这篇主要讲了一下setState的异步更新处理过程。

我们的更新其实并不是真正的异步处理,而是更新的时候把更新内容放到了更新队列中,最后批次更新,这样才表现出异步更新的状态。setTimeout,promise等异步方法可以直接跳过批量处理机制,setState调几次就改几次。

猜你喜欢

转载自www.cnblogs.com/lyt0207/p/12677651.html