zoukankan      html  css  js  c++  java
  • 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调几次就改几次。

    不积跬步无以至千里
  • 相关阅读:
    MyISAM表锁的解决方案
    RSA数字证书管理
    Self Host WebApi服务传输层SSL加密(服务器端+客户端调用)
    WebApi服务Uri加密及验证的两种方式
    利用MVC的自定义过滤器FilterAttribute、IActionFilter、IExceptionFilter实现异常处理等功能
    html页面中meta的作用
    [转]REST简介
    [转]webApi 参数传递总结
    REST服务中的异常处理
    REST服务返回自定义的HttpResponseMessage
  • 原文地址:https://www.cnblogs.com/lyt0207/p/12677651.html
Copyright © 2011-2022 走看看