react setState principle

  • The data source of the component has two places, namely the attribute object and the state object
  • The property is passed from the parent component and cannot be changed
  • The state is internal, and the only way to change the state is setState
  • Changes in attributes and states will cause the view to update
Import React from "REACT" ; 
Import ReactDOM from "REACT-dom" ; 

/ * * 
 * property is transferred from the parent component over, can not change 
 the only way * state is an internal component, maintained by yourself, the outside world can not access the change of state is setState 
 * / 
class Counter extends React.Component { 
  constructor (props) { // The constructor is the only place where the state is assigned 
    super (props)
     // The place where the state is defined 
    this.state = {number: 0 } 
  } 
  render () { 
    // When we call setState, it will cause state change and component update 
    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'))

The constructor is the only place that defines the state and assigns values. When we want to change the value of the state, we need to pass the setState method instead of directly modifying the value of the state. Each time setState is called, it will cause a state change and component update.

1. The value of state cannot be modified directly

Import React from "REACT" ; 
Import ReactDOM from "REACT-dom" ; 

/ * * 
 * property is transferred from the parent component over, can not change 
 the only way * state is an internal component, maintained by yourself, the outside world can not access the change of state is setState 
 * / 
class Counter extends React.Component { 
  constructor (props) { // The constructor is the only place to assign a value to the state 
    super (props)
     // The place to define the state 
    this .state = {number: 0 } 
  } add
   = () => { 
    this.state.number + = 1 
  } 
  render () { 
    // When we call setState, it will cause state changes and component updates 
    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'))

Methods like this directly modify state worth will not take effect.

 2. The state update may be asynchronous

We know that calling setState will trigger an update operation. This process includes updating the state, creating a new VNode, comparing the differences through the diff algorithm, and deciding which part needs to be rendered. If he is synchronously updated, each call must execute the previous process This will cause a lot of performance problems, so you need to put multiple setState into a queue, and then execute one by one, and finally update the view at once, which will improve performance. for example:

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调几次就改几次。

Guess you like

Origin www.cnblogs.com/lyt0207/p/12677651.html