React的useState是同步还是异步?

2,326 阅读3分钟

前言

在开发的过程中,对state在函数取值,以及渲染的次数一知半解,了解了一部分关于state的内容,导致在使用上有些问题,因此写了几个案例,来全面的总结一下state的使用。

一.案例之前先总结一下关于state使用(react17之后)

  • 在react的合成事件或者js原生事件操作state分为两种不同的情况
  • 在同步函数或者异步函数中操作state分为两种不同情况

二.案例

下面分为四个案例,来分别查看在react操作state时的取值情况,以及渲染次数。

1.react合成事件中操作state

import React ,{useState} from "react";

export default function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);

function handleClick() {
  setCount(count+ 1);
  console.log(count); 
  setCount(count+1);
  console.log(count); 
  setFlag(f => !f);
}
console.log('render')
return (
    <div>
      <button onClick={handleClick}>Next</button>
      <h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
    </div>
);
}

点击一次的结果

image.png 从上面的结果看,在React合成事件中,同步操作下的结果看:

  • 1.setCount两次,两次的count都为0,所以即便是serCount(count+1)两次,最终展示的结果是1
  • 2.在引入了两个state,并进行了改变,最终渲染了一次

2.在JS原生事件中操作state

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

export default function App() {
    const [count, setCount] = useState(0);
    const [flag, setFlag] = useState(false);
    useEffect(() => {
        document.getElementById('button').onclick= () => {
            setCount(count + 1);
            console.log(count)
            setCount(count + 1);
            console.log(count);
            setFlag(!flag);
        }
    }, [])

    console.log('render')
    return (
        <div>
            <button id='button'>Next</button>
            <h1 style={{color: flag ? "blue" : "black"}}>{count}</h1>
        </div>
    );
}

点击一次的结果 image.png 从上面的结果看,在原声操作下的结果看:

  • 1.setCount两次,两次的count都为0,所以即便是serCount(count+1)两次,最终展示的结果是1
  • 2.在引入了两个state,并进行了3次state改变,最终渲染了3次(结果图的第一行log的‘render’是初始化)

3.在异步函数中操作state

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

export default function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);
  useEffect(()=>{
      setTimeout(()=>{
          setCount(count+1);
          console.log(count);
          setCount(count+1);
          console.log(count)
          setFlag(f => !f);
      })
  },[])

  console.log('render')
  return (
      <div>
        <button>Next</button>
        <h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
      </div>
  );
}

渲染结果 image.png 从上面的结果看,在异步函数操作下的结果看:

  • 1.setCount两次,两次的count都为0,所以即便是serCount(count+1)两次,最终展示的结果是1
  • 2.在引入了两个state,并进行了3次state改变,最终渲染了3次(结果图的第一行log的‘render’是初始化)

4.在React合成异步操作state

import React, {useState} from "react";

export default function App() {
    const [count, setCount] = useState(0);
    const [flag, setFlag] = useState(false);

    async function handleClick() {
        await setCount(count + 1);
        console.log(count);
        await setCount(count + 1);
        await console.log(count)
        setFlag(f => !f);
    }

    console.log('render')
    return (
        <div>
            <button onClick={handleClick}>Next</button>
            <h1 style={{color: flag ? "blue" : "black"}}>{count}</h1>
        </div>
    );
}

点击一次结果: image.png 从上面的结果看,在合成事件异步函数操作下的结果看:

  • 1.setCount两次,两次的count都为0,所以即便是serCount(count+1)两次,最终展示的结果是1
  • 2.在引入了两个state,并进行了3次state改变,最终渲染了3次(结果图的第一行log的‘render’是初始化)

总结

  1. 在异步调用的前提下,state是同步的,每次改变都会触发渲染
  2. 在原声事件中,state是同步的,每次改变都会触发渲染
  3. 在合成事件中,state是异步的,在方法执行完之后进行渲染

思考:为什么state在不同的条件下的策略是不同的呢? 这个问题是关于react的性能优化机制,避免重复的渲染,通过批处理的方式来实现。

参考:github.com/reactwg/rea…