React 中 setState 是同步还是异步 ? 如何控制 ?

50 阅读4分钟

@[TOC]

React 中 setState 是同步还是异步 ? 如何控制 ?

setState是异步还是同步与react批量更新有很大关系,react为了优化性能,采用的是批量更新,状态队列更新机制

先看异步情况:

import React, { Component } from 'react';
​
class com2 extends 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 com2;
​

在这里插入图片描述

在这里插入图片描述

react 内部为了优化 setStae的批量处理,会对 setState 进行一个批量处理的原则,并且对相同的属性进行合并保留最后一次进行更新,所以按钮上面是 3 可以理解的

但是打印出来三次 0 这是为啥 ?

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

总结一下:

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

控制 setState 的同步异步:

上面说了 setState是异步的原因是因为走了react内部合并的一个逻辑 因此为了达到同步那只能绕开react内部合并的一个逻辑不让它进入 updateQueue那么它就成了同步的 因为setState()本身就是同步的

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

import React, { Component } from 'react';
class com2 extends 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 com2;
​

结果如下:

在这里插入图片描述

在这里插入图片描述

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

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

值得一提的是

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

传递对象 批处理,对相同变量进行的多次处理会合并为一个,并以最后一次的处理结果为准

传递函数 链式调用,React 会把我们更新 state 的函数加入到一个队列里面,然后,按照函数的顺序依次调用。同时,为每个函数传入 state 的前一个状态,这样,就能更合理的来更新我们的 state 了,该函数有两个参数

prevState

props

可以理解为:

本来我想拿到上一次setState() 执行完后的结果,需要使用一些特殊的方式,绕开合并逻辑,让setState() 保持本身的同步执行特性,代码如下:

import React, { Component } from 'react';
​
class com2 extends Component {
​
    state = {
        count: 0
    }
​
    add = () => {
        setTimeout(()=>{
            this.setState({
                count: this.state.count + 1
            })
            console.log(this.state.count)
            this.setState({
                count: this.state.count + 1
            })
            console.log(this.state.count)
            this.setState({
                count: this.state.count + 1
            })
            console.log(this.state.count)
        })
​
    }
​
    render() {
        return (
            <div>
                <button onClick={this.add}>按钮{this.state.count}</button>
            </div>
        );
    }
}
​
export default com2;
​

点击后结果:

在这里插入图片描述

在这里插入图片描述

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

import React, { Component } from 'react';
​
class com2 extends 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 + 1 }
        });
        console.log(this.state.count)
        this.setState((state) => {
            // 重要:在更新的时候读取 `state`,而不是 `this.state`。
            return { count: state.count + 1 }
        });
        console.log(this.state.count)
​
    }
​
    render() {
        return (
            <div>
                <button onClick={this.add}>按钮{this.state.count}</button>
            </div>
        );
    }
}
​
export default com2;
​

结果如下:

在这里插入图片描述

在这里插入图片描述

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