setState 是同步还是异步 测试

class 类组件中的 setState 和 hooks 函数组件中的 useState 的 状态修改函数 是一样的,有时同步,有时异步。

结论:

  1. setState 只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的。
  2. setState 的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
  3. setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和 setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新。
  • 原生事件: 指的是绕过 React 通过 addEventListener 直接添加的事件处理函数
  • 钩子函数: 各个生命周期函数
  • 合成事件:由 React 引发的事件处理(比如通过 onClick 引发的事件处理)

React是怎样控制异步和同步的呢?

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

总结:

只要你进入了 react 的调度流程,那就是异步的。只要你没有进入 react 的调度流程,那就是同步的。什么东西不会进入 react 的调度流程? setTimeout setInterval ,直接在 DOM 上绑定原生事件等。这些都不会走 React 的调度流程,你在这种情况下调用 setState ,那这次 setState 就是同步的。 否则就是异步的。
而 setState 同步执行的情况下, DOM 也会被同步更新,也就意味着如果你多次 setState ,会导致多次更新,这是毫无意义并且浪费性能的。

类组件测试:

import React, { PureComponent } from "react";

class setStateTest extends PureComponent {
  constructor() {
    super();
    this.state = {
      num: 0,
      str: "111",
      bool: false
    }
  };

  componentDidMount() {
// 在 componentDidMount 生命周期里,连续修改 state ,只会触发一次render,state 被放在任务队列中批量更新,此时它们是异步的。
    this.setState({
      num: 3
    })
    console.log(this.state.num); // 0
    this.setState({
      str: "222"
    })
    this.setState({
      num: 2
    })
    console.log(this.state.num); // 0

// 在定时器里会逐条触发 render ,说明此时是同步
    setTimeout(() => { 
      this.setState({
        num: 3
      })
      console.log(this.state.num); // 3
      this.setState({
        str: "222"
      })
      this.setState({
        num: 2
      })
      console.log(this.state.num); // 2
    }, 5000)
  };

  render() {
    const { num, str, bool } = this.state;
    console.log("触发render", num, str, bool);
    return (
      <>
        <button onClick={() => { // 两个 setState 都执行了,但是只刷新了一次,所以它是异步的?
          this.setState({
            num: num + 1
          })
          this.setState({
            bool: !bool
          })
        }}>setState同步异步测试</button>
      </>
    )
  };
};

export default setStateTest;

函数式组件测试:

import React, { useEffect, useState } from "react";

export default function setStateTest() {
  const [num, setNum] = useState(0);
  const [str, setStr] = useState('11111');

  useEffect(() => {
    // 异步处理,会合并计算,值触发一次 刷新
    setNum(2)
    console.log(num); // 0 因为是异步处理,所以这里打印的是初始值 0
    setNum(3)
    console.log(num); // 0
    setNum(4)
    setStr("22222")

    // 在定时器里会逐条触发 render ,说明此时是同步
    // 为何 hooks 组件中 定时器 里面没有拿到更新后的值,这里是因为闭包的原因,只能拿到 初始 值 0
    setTimeout(() => {
      setNum(2)
      console.log(num); // 0
      setNum(3)
      console.log(num); // 0
      setNum(1)
      setStr("33333")
    }, 3000)
  }, [])

  return (
    <>
      <button onClick={() => {
        setTimeout(() => { // 在定时器里会逐条触发 render ,说明此时是同步
          setNum(2)
          console.log(num); // 看点击的时候,当前的 num 是多少就是多少
          setNum(3)
          console.log(num); // 同上
          setNum(1)
          setStr("33333")
        }, 0)
      }}>{console.log("刷新了")}点击事件</button>
    </>
  )
};
posted @ 2022-04-29 11:31  真的想不出来  阅读(270)  评论(0编辑  收藏  举报