• 深入理解React中 setState 的同步与异步行为
  • 发布于 2个月前
  • 168 热度
    0 评论
React 中 setState 的同步与异步行为
在 React 中,setState 的行为并非总是同步或总是异步,而是取决于它被调用的上下文环境。理解这一点对于编写高效且无 bug 的 React 应用至关重要。
1. 异步更新(批量更新)
场景: 在 React 的事件处理函数(例如 onClick、onChange 等)以及生命周期函数(例如 componentDidMount、componentDidUpdate)中调用 setState 时,React 会将状态更新放入一个队列中,然后批量更新。
原因: 这样做是为了提高性能。如果每次 setState 都立即更新 DOM,可能会导致大量的重渲染,影响用户体验。批量更新可以将多次状态更新合并为一次,减少 DOM 操作的次数。
表现: 在事件处理函数或生命周期函数中,多次调用 setState,在函数执行完毕后,只会触发一次组件的重新渲染,并且状态值会是最后一次 setState 设置的值。
示例
import React, { useState } from 'react';
function AsyncUpdateExample() {
  const [count, setCount] = useState(0);
   // 堆代码 duidaima.com
  const handleClick = () => {
    console.log('handleClick start', count);
    setCount(count + 1);
    console.log('setCount + 1', count);
    setCount(count + 1);
    console.log('setCount + 1 again', count);
    setCount(count + 1);
    console.log('setCount + 1 again again', count);
    // 此时 count 的值仍然是 0,因为 setState 是异步的
    console.log('handleClick end', count);
  };
  console.log('render', count);
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}
export default AsyncUpdateExample;
代码解释:
AsyncUpdateExample 组件使用 useState hook 初始化 count 状态为 0。
handleClick 函数中,我们连续三次调用 setCount,每次都将 count 加 1。
在 handleClick 函数内部,我们打印了 count 的值,你会发现每次打印都是 0,因为 setState 是异步的,状态更新不会立即生效。
在 handleClick 函数执行完毕后,React 会将这三次 setCount 合并为一次更新,最终 count 的值会变为 3,组件重新渲染,并且控制台会打印 render 3。
控制台输出如下:
render 0
handleClick start 0
setCount + 1 0
setCount + 1 again 0
setCount + 1 again again 0
handleClick end 0
render 3
2. 同步更新
场景: 在 React 事件处理函数之外,例如在原生 JavaScript 事件处理函数、setTimeout、setInterval、Promise 的 then 回调函数中调用 setState 时,React 会同步更新状态。
原因: 在这些情况下,React 无法控制代码的执行流程,因此无法进行批量更新。为了保证状态的正确性,React 会立即更新状态。
表现: 在这些函数中,每次调用 setState 都会立即触发组件的重新渲染,并且状态值会立即更新。
示例
import React, { useState, useEffect } from 'react';

function SyncUpdateExample() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const timer = setTimeout(() => {
      console.log('setTimeout start', count);
      setCount(count + 1);
      console.log('setCount + 1', count);
      setCount(count + 1);
      console.log('setCount + 1 again', count);
      setCount(count + 1);
      console.log('setCount + 1 again again', count);
      console.log('setTimeout end', count);
    }, 0);
    return () => clearTimeout(timer);
  }, []);
  console.log('render', count);
  return (
    <div>
      <p>Count: {count}</p>
    </div>
  );
}
export default SyncUpdateExample;
代码解释:
SyncUpdateExample 组件使用 useState hook 初始化 count 状态为 0。
useEffect hook 中,我们使用 setTimeout 模拟一个异步操作。
在 setTimeout 的回调函数中,我们连续三次调用 setCount,每次都将 count 加 1。
在 setTimeout 的回调函数内部,我们打印了 count 的值,你会发现每次打印的值都是上一次 setCount 更新后的值,因为 setState 是同步的。
每次 setCount 都会触发组件的重新渲染。
控制台输出如下:
render 0
setTimeout start 0
setCount + 1 0
render 1
setCount + 1 again 1
render 2
setCount + 1 again again 2
render 3
setTimeout end 2
3. 使用函数式更新
场景: 当你的状态更新依赖于之前的状态时,应该使用函数式更新。
原因: 由于 setState 的异步行为,直接使用 count + 1 可能会导致状态更新不正确。函数式更新可以确保你获取到的是最新的状态值。
示例
import React, { useState } from 'react';

function FunctionalUpdateExample() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount((prevCount) => prevCount + 1);
    setCount((prevCount) => prevCount + 1);
    setCount((prevCount) => prevCount + 1);
  };

  console.log('render', count);
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}
export default FunctionalUpdateExample;
代码解释:
FunctionalUpdateExample 组件使用 useState hook 初始化 count 状态为 0。
handleClick 函数中,我们连续三次调用 setCount,每次都使用函数式更新,确保获取到的是最新的 prevCount。
最终 count 的值会变为 3,组件重新渲染。
控制台输出如下:
render 0
render 3
总结
场景setState 行为状态更新时机React 事件处理函数异步批量更新生命周期函数异步批量更新原生 JavaScript 事件处理函数同步立即更新setTimeout、setInterval 回调函数同步立即更新Promise 的 then 回调函数同步立即更新。
最佳实践
使用函数式更新: 当状态更新依赖于之前的状态时,使用函数式更新 setCount(prevCount => prevCount + 1)。
避免在 setState 后立即读取状态: 由于 setState 的异步性,不要在 setState 后立即读取状态,因为此时状态可能还没有更新。
理解批量更新: 了解 React 的批量更新机制,避免不必要的重新渲染。

代码示例总结
三个代码示例,分别演示了 setState 的异步更新、同步更新以及函数式更新。这些示例可以帮助您更好地理解 setState 的行为。
希望这个详细的解释能够帮助您理解 React 中 setState 的同步和异步行为。如果您有任何其他问题,请随时提出。
用户评论