You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

普通setState与函数式setState的异步场景表现差异解析

React状态更新行为差异解析

代码片段 #1

import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

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

  useEffect(() => {
    console.log("updating count...", count)
  }, [count])

  function handleClickAsync() {
    setTimeout(function delay() {
      setCount(prevCount => prevCount + 1);
    }, 3000);
  }

  return (
    <div>
      {count}
      <button onClick={handleClickAsync}>Increase async</button>
    </div>
  );
}

代码片段 #2

import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

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

  useEffect(() => {
    console.log("updating count...", count)
  }, [count])

  function handleClickAsync() {
    setTimeout(function delay() {
      setCount(count + 1); //UPDATED THIS PART OF CODE!!!
    }, 3000);
  }

  return (
    <div>
      {count}
      <button onClick={handleClickAsync}>Increase async</button>
    </div>
  );
}

问题

快速点击按钮3次后,代码片段1中useEffect内的console.log执行3次;代码片段2中将setCount改为普通状态更新后,点击3次useEffect仅执行一次。为何React在两种场景下的状态更新表现不同?

原因解析

核心差异来自状态更新的依赖逻辑和闭包对变量的捕获特性:

  • 代码片段1:函数式更新
    每次点击按钮时,setTimeout的回调会捕获到setCount函数(React保证这个函数的引用始终稳定)。3秒后回调执行时,函数式更新会直接从React内部获取当前最新的count值,每次更新都是基于最新状态加1。三次点击触发的三个异步回调,各自执行时都会触发一次独立的状态变更,每次变更后count值变化,都会触发useEffect执行,所以最终console.log会跑3次。

  • 代码片段2:直接值更新
    每次点击按钮时,setTimeout的回调会捕获到点击瞬间的count值(这是闭包的特性)。假设三次点击时count都是0,三个回调捕获的count值就都是0。3秒后三个回调依次执行,每次都试图把count设为0+1=1——这三次更新的目标值完全相同,React会自动合并这类重复的状态更新,最终只触发一次状态变更,useEffect自然也就只执行一次。

另外补充:在setTimeout这类React无法直接控制的异步回调中,React不会开启批量更新(批量更新仅在React可控的同步事件流,比如按钮点击的同步回调中生效),所以代码片段1的三次更新是依次触发的,而代码片段2的三次更新因为目标值重复被合并。


内容的提问来源于stack exchange,提问作者D_S_X

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 02:15:40