关于setState的那点事


一、setState执行初探

事件里的setState

请说出以下代码按钮点击之后输出的值:

import React, {
    
     Component } from 'react'

export default class App extends Component {
    
    
    state = {
    
    
        num :0
    }

    handleClick=()=>{
    
    
        this.setState({
    
    
            num:this.state.num + 1
        })
        console.log(this.state.num)
    }
    
    render() {
    
    
        return (
            <div>
                <h1>
                    {
    
    this.state.num}
                </h1>
                <p>
                    <button onClick={
    
    this.handleClick}>+</button>
                </p>
            </div>
        )
    }
}

按照逻辑,setState一调用便调用render函数,立刻把state的值进行改变,所以你是否认为输出语句的结果是1?

我们看看结果:
输出结果
我们再次点击,看看输出结果:

输出结果
看到这里我们猜测setState的执行是异步的,我们在render函数里写上输出语句试试:

import React, {
    
     Component } from 'react'

export default class App extends Component {
    
    
    state = {
    
    
        num :0
    }

    handleClick=()=>{
    
    
        this.setState({
    
    
            num:this.state.num + 1
        })
        console.log(this.state.num)
    }
    
    render() {
    
    
        console.log('render')
        return (
            <div>
                <h1>
                    {
    
    this.state.num}
                </h1>
                <p>
                    <button onClick={
    
    this.handleClick}>+</button>
                </p>
            </div>
        )
    }
}

我们点击一下,看看输出结果:
输出结果
再次点击:
输出结果
到这里我们可以初步确认setState的执行可能是异步的。

定时器里的setState

同样的,我们看看下面的代码,猜猜它的输出结果:

import React, {
    
     Component } from 'react'

export default class App extends Component {
    
    
    state = {
    
    
        num: 0
    }

    componentDidMount() {
    
    
        window.setInterval(() => {
    
    
            this.setState({
    
    
                num: this.state.num + 1
            })
        }, 1000)
        console.log(this.state.num)
    }

    render() {
    
    
        console.log("render")
        return <div>
            <h1>{
    
    this.state.num}</h1>
        </div>
    }
}

根据经验,如果setState的执行是异步的,那么这里的执行结果应该是先输出0,再输出render,让我们看看输出结果:
输出结果
啊这?那如何判断setState是同步还是异步的?

二、setState的使用

setState的第二个参数(callback)

对于某些场景,比如需要在setState之后做某些事,这些事又是依据setState之后的结果,那么这个时候可以借助setState的第二个参数了,这个参数是一个回调函数,在setState执行之后调用。

比如上面的点击例子,如果我们需要在点击之后输出setState最新的值,可以进行以下写法:

import React, {
    
     Component } from 'react'

export default class App extends Component {
    
    
    state = {
    
    
        num: 0
    }

    handleClick=()=>{
    
    
        this.setState({
    
    
            num:this.state.num + 1
        },()=>{
    
    
            console.log(this.state.num)
        })
    }
    
    render() {
    
    
        console.log('render')
        return (
            <div>
                <h1>
                    {
    
    this.state.num}
                </h1>
                <p>
                    <button onClick={
    
    this.handleClick}>+</button>
                </p>
            </div>
        )
    }
}

我们点击一次之后,看看输出结果:
输出结果
所以,对于需要在setState之后做点什么事情,可以在其第二个参数callback进行。

关于多个setState的执行

说说下面代码的输出结果:

import React, {
    
     Component } from 'react'

export default class App extends Component {
    
    
    state = {
    
    
        num: 0
    }

    handleClick=()=>{
    
    
        this.setState({
    
    
            num:this.state.num + 1
        })
        this.setState({
    
    
            num:this.state.num + 1
        })
        this.setState({
    
    
            num:this.state.num + 1
        })
        console.log(this.state.num)  
    }
    
    render() {
    
    
        console.log('render')
        return (
            <div>
                <h1>
                    {
    
    this.state.num}
                </h1>
                <p>
                    <button onClick={
    
    this.handleClick}>+</button>
                </p>
            </div>
        )
    }
}

如果按照往常的逻辑,我们猜测点击之后,页面会输出3,控制台输出0还有render。

我们看看结果:
输出结果
再看看页面:
输出结果

没错,按照setState在事件异步执行的逻辑,我们可以确定控制台的执行结果,可是页面的输出结果却不是3,而是1。

我们换种方式书写代码:

import React, {
    
     Component } from 'react'

export default class App extends Component {
    
    
    state = {
    
    
        num: 0
    }

    handleClick = () => {
    
    
        this.setState({
    
    
            num: this.state.num + 1
        }, () => {
    
     console.log(this.state.num) })
        this.setState({
    
    
            num: this.state.num + 1
        }, () => {
    
     console.log(this.state.num) })
        this.setState({
    
    
            num: this.state.num + 1
        }, () => {
    
     console.log(this.state.num) })
    }
    
    render() {
    
    
        console.log('render')
        return (
            <div>
                <h1>
                    {
    
    this.state.num}
                </h1>
                <p>
                    <button onClick={
    
    this.handleClick}>+</button>
                </p>
            </div>
        )
    }
}

点击一次按钮,看看页面输出结果:
页面输出结果

控制台输出结果:
控制台输出结果
在这里我们可以初步判断多个setState运行时,只会合并触发一次render。

setState第一个参数(函数写法)

假如我们有这样的需求,需要每次setState时用的状态是上一次setState的状态,即setState的同步写法,这个时候你可能想到了用回调函数去处理,即如下代码:

import React, {
    
     Component } from 'react'

export default class App extends Component {
    
    
    state = {
    
    
        num: 0
    }

    handleClick = () => {
    
    
        this.setState({
    
    
            num: this.state.num + 1
        }, () => {
    
    
            console.log(this.state.num)
            this.setState({
    
    
                num: this.state.num + 1
            }, () => {
    
    
                console.log(this.state.num)
                this.setState({
    
    
                    num: this.state.num + 1
                }, () => {
    
    
                    console.log(this.state.num)
                })
            })
        })
    }
    
    render() {
    
    
        console.log('render')
        return (
            <div>
                <h1>
                    {
    
    this.state.num}
                </h1>
                <p>
                    <button onClick={
    
    this.handleClick}>+</button>
                </p>
            </div>
        )
    }
}

看到这里相信很多小伙伴知道答案了,我们看看点击之后输出结果吧。

控制台输出结果:
控制台输出结果
页面输出结果:
页面输出结果
重点来了!!!针对上面这个示例,其实有更加优雅的处理方式,setState第一个参数除了是对象,还可以是一个函数!(该函数有个参数,参数为当前状态,该函数的返回值会覆盖掉之前的状态,并且该函数是异步执行的。

import React, {
    
     Component } from 'react'

export default class App extends Component {
    
    
    state = {
    
    
        num: 0
    }

    handleClick = () => {
    
    
        this.setState(state => {
    
    
            console.log(this.state.num)
            return {
    
    
                num: state.num + 1
            }
        }
        )

        this.setState(state => {
    
    
            console.log(this.state.num)
            return {
    
    
                num: state.num + 1
            }
        }
        )

        this.setState(state => {
    
    
            console.log(this.state.num)
            return {
    
    
                num: state.num + 1
            }
        }
        )
    }
    
    render() {
    
    
        console.log('render')
        return (
            <div>
                <h1>
                    {
    
    this.state.num}
                </h1>
                <p>
                    <button onClick={
    
    this.handleClick}>+</button>
                </p>
            </div>
        )
    }
}

点击之后,我们看看控制台输出结果:
控制台输出结果
页面输出结果:
页面输出结果
这时我们可以明白setState的“同步”写法了,但是对this.state的状态可能不知不解。

我们尝试修改成如下代码:

import React, {
    
     Component } from 'react'

export default class App extends Component {
    
    
    state = {
    
    
        num: 0
    }

    handleClick = () => {
    
    
        this.setState(state => {
    
    
            console.log(state.num)
            return {
    
    
                num: state.num + 1
            }
        }
        )

        this.setState(state => {
    
    
            console.log(state.num)
            return {
    
    
                num: state.num + 1
            }
        }
        )

        this.setState(state => {
    
    
            console.log(state.num)
            return {
    
    
                num: state.num + 1
            }
        }
        )
    }
    
    render() {
    
    
        console.log('render')
        return (
            <div>
                <h1>
                    {
    
    this.state.num}
                </h1>
                <p>
                    <button onClick={
    
    this.handleClick}>+</button>
                </p>
            </div>
        )
    }
}

点击之后看看控制台输出结果:
控制台输出结果
可以看出,函数的参数为上一次return的对象,这种写法类似于中间件,一个传递一个。

划个重点!!!

上面的做法react实际上只会对异步的setState进行这样处理,通常情况下html事件的处理是异步的(事件的处理一般会操作很多数据,包括状态,而事件对state的更改往往是频繁的,将其进行异步处理本质上是一种优化),而比如定时器这种非html事件是同步的。

setState里的回调函数callback运行的时机也会是所有的setState状态更改完成才运行

实际上react对setState的处理会在内部维护一个队列,然后进行对比并一次性render


三、浅谈setState源码

关于setState源码,博主会在后续的源码研究更新修改,以下是官网部分源码。
让我们看看官方文档里的setState:

Component.prototype.setState = function(partialState, callback) {
    
    
//partialState为传入的混入对象,callback为回调函数

  invariant(
    typeof partialState === 'object' ||
      typeof partialState === 'function' ||
      partialState == null,
  );//该函数判断传入的值是否为不符合条件,不符合条件会报错
  
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
  //updater是在构造函数中定义的一个变量,从调用的方法语义可以看出,调用setState更新的状态会被加入到一个队列中。
};

查看enqueueSetState方法:

  enqueueSetState: function(publicInstance, currentPartialState) {
    
    
        if (queue === null) {
    
    
          warnNoop(publicInstance, 'setState');
          return null;
        }
        queue.push(currentPartialState);  //往队列push状态
      },

总结

  1. html事件的setState是异步的,非事件如定时器是同步处理的。
  2. 如果需要在改变状态之后做点事,可以使用回调函数,即setState的第二个参数。
  3. 如果要使用上一次setState之后的状态,可以使用函数的方式改变状态,即setState第一个参数使用函数的形式。

猜你喜欢

转载自blog.csdn.net/Mr_RedStar/article/details/116052562