【React】setState是同步的还是异步的?

2023-11-05

异步情况下的setState()

import React,{Compontent} from 'react';

class Mydemo extends React.Component {
    state = {
        num: 0
    };

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

        this.setState({
            num: this.state.num + 2
        })
        console.log(this.state.num);

        this.setState({
            num: this.state.num + 3
        })
        console.log(this.state.num);
    }

    render() {
        return (
            <div>
                <button onClick={this.add}>按钮{this.state.num}</button>
            </div>
        )
    }
}

export default Mydemo;

结果如下:
在这里插入图片描述

react内部为了优化setState()的批处理,会对setState()进行合并,并且对相同属性的设置只保留最后一次的设置。
所以会打印按钮3、按钮6、按钮9

但第一次打印出3次0,是为什么?

在 React 的 setState 函数实现中,会根据一个变量 isBatchingUpdates判断是直接更新 this.state 还是放到一个updateQueue中延时更新。
而 isBatchingUpdates 默认是 false,表示 setState 会同步更新 this.state。
但是,有一个函数 batchedUpdates,该函数会把 isBatchingUpdates 修改为 true。
而当 React 在调用事件处理函数之前就会先调用这个 batchedUpdates将isBatchingUpdates修改为true。
这样由 React 控制的事件处理过程 setState 不会同步更新 this.state,而是异步的。

总结:

所以说setstate本身是同步的,一旦走了react内部的合并逻辑,放入了updateQueue队列中就变成异步了。
而代码中的函数是react控制的,内部会走合并逻辑,所以这里的setState 不但是合并的也是异步的,所以打印出三个0


控制setState的同步和异步

上面说了,setState是异步的原因是因为走了react内部的合并逻辑,那只要能绕过react内部的合并逻辑,不让它进入到updateQueue中不就变成同步了吗? 因为setState()本身就是同步的

利用setTimeout绕过react内部的合并逻辑:

import React,{Compontent} from 'react';

class Mydemo extends React.Component {
    state = {
        num: 0
    };

    add = () => {
        //利用setTimeout绕过react的控制,不让setState()走合并逻辑
        setTimeout(() => {
            this.setState({
                num: this.state.num + 1
            })
            console.log(this.state.num)

            this.setState({
                num: this.state.num + 2
            })
            console.log(this.state.num)

            this.setState({
                num: this.state.num + 3
            })
            console.log(this.state.num)
        });
    }

    render() {
        return (
            <div>
                <button onClick={this.add}>按钮{this.state.num}</button>
            </div>
        )
    }
}

export default Mydemo;

结果如下:
在这里插入图片描述


setState()同步、异步总结

异步的情况:

  • 由React控制的事件处理函数,以及生命周期函数调用setState时表现为异步 。
  • 大部分开发中用到的都是React封装的事件,比如onChange、onClick、onTouchMove等(合成事件中),这些事件处理函数中的setState都是异步处理的。

同步的情况:

  • React控制之外的事件中调用setState是同步更新的。
  • 比如原生js绑定的事件,setTimeout/setInterval,ajax,promise.then内等 React 无法掌控的 APIs情况下,setState是同步更新state的

setState()的参数接收

setState()可以接收一个对象外,还可以接收一个函数。

区别:

  • 传递对象
    批处理,对相同变量进行的多次处理会合并为一个,并以最后一次的处理结果为准
  • 传递函数
    链式调用,React 会把我们更新 state 的函数加入到一个队列里面,然后,按照函数的顺序依次调用。
    同时,为每个函数传入 state 的前一个状态,这样,就能更合理的来更新我们的 state 了,该函数有两个参数:prevState props

传递函数时可以理解为:
本来我想拿到上一次setState() 执行完后的结果,需要使用一些特殊的方式,绕开合并逻辑,让setState() 保持本身的同步执行特性。

在异步执行的情况下,我还想拿到上一次setState执行完后的结果,react给我们提供了方式,就是在setState()里面传入一个函数。

import React,{Compontent} from 'react';

class Mydemo extends React.Component {
    state = {
        count: 0
    };

    add = () => {
        this.setState((state) => {
            // 重要:在更新的时候读取 `state`,而不是 `this.state`。
            return { count: state.count + 1 }
        });
        console.log(this.state.count)
        this.setState((state) => {
            // 重要:在更新的时候读取 `state`,而不是 `this.state`。
            return { count: state.count + 2 }
        });
        console.log(this.state.count)
        this.setState((state) => {
            // 重要:在更新的时候读取 `state`,而不是 `this.state`。
            return { count: state.count + 3 }
        });
        console.log(this.state.count)
    }

    render() {
        return (
            <div>
                <button onClick={this.add}>按钮{this.state.count}</button>
            </div>
        )
    }
}

export default Mydemo;

在这里插入图片描述
在setState中传一个函数,能拿到上次setState执行完后的结果,但是不妨碍是异步更新的,可以看到打印的是0,这是react给我们提供的方便之处。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【React】setState是同步的还是异步的? 的相关文章