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

关于JavaScript闭包运行机制及React代码输出异常的技术咨询

关于JavaScript闭包运行机制及React代码输出异常的技术咨询

嘿,我来帮你拆解这个问题!你这段React代码里,定时器每次打印的都是0 1而非count和increment的当前值,核心问题出在闭包的变量捕获特性,再加上对useEffectEvent的用法误解,咱们一步步理清楚:

问题根源分析

  1. 闭包的作用域捕获
    组件首次渲染时,useEffect的依赖是空数组[],所以只会执行一次。调用sI()时,定时器的回调函数会捕获当前作用域下的count(初始值0)和increment(初始值1)。
    后续组件重新渲染时,虽然count和increment的值会更新,但它们是新的变量(每个渲染周期都会创建新的状态变量),而定时器回调依然绑定在首次渲染的作用域上,所以始终打印初始的0 1。

  2. useEffectEvent的用法误区
    useEffectEvent的设计初衷是把需要访问最新状态的逻辑与effect的依赖解耦,但你把整个定时器逻辑都塞进了useEffectEvent里,这并没有改变闭包捕获的本质——这个函数依然是在首次渲染时创建的,内部的count和increment还是初始值。

解决方案

方案一:用useRef保存最新状态值

通过useRef来存储count和increment的最新引用,确保定时器能访问到实时值:

import { useState, useEffect, useRef } from 'react';

export default function Timer() {
  const [count, setCount] = useState(0);
  const [increment, setIncrement] = useState(1);

  // 用ref保存最新状态
  const countRef = useRef(count);
  const incrementRef = useRef(increment);

  // 状态更新时同步ref的值
  useEffect(() => {
    countRef.current = count;
  }, [count]);

  useEffect(() => {
    incrementRef.current = increment;
  }, [increment]);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(c => c + incrementRef.current);
      // 打印最新值
      console.log(countRef.current, incrementRef.current);
    }, 1000);
    return () => clearInterval(id);
  }, []);

  return (
    <>
      <h1>
        Counter: {count}
        <button onClick={() => setCount(0)}>Reset</button>
      </h1>
      <hr />
      <p>
        Every second, increment by:
        <button disabled={increment === 0} onClick={() => {
          setIncrement(i => i - 1);
        }}>–</button>
        <b>{increment}</b>
        <button onClick={() => {
          setIncrement(i => i + 1);
        }}>+</button>
      </p>
    </>
  );
}

方案二:正确使用useEffectEvent

把需要访问最新状态的逻辑抽离到useEffectEvent中,让effect里的定时器只负责触发逻辑,而非捕获状态:

import { useState, useEffect } from 'react';
import { experimental_useEffectEvent as useEffectEvent } from 'react';

export default function Timer() {
  const [count, setCount] = useState(0);
  const [increment, setIncrement] = useState(1);

  // 用useEffectEvent包裹需要访问最新状态的逻辑
  const getLatestValues = useEffectEvent(() => {
    console.log(count, increment);
    return increment; // 返回最新的增量值
  });

  useEffect(() => {
    const id = setInterval(() => {
      // 调用getLatestValues获取最新状态
      setCount(c => c + getLatestValues());
    }, 1000);
    return () => clearInterval(id);
  }, []);

  return (
    <>
      <h1>
        Counter: {count}
        <button onClick={() => setCount(0)}>Reset</button>
      </h1>
      <hr />
      <p>
        Every second, increment by:
        <button disabled={increment === 0} onClick={() => {
          setIncrement(i => i - 1);
        }}>–</button>
        <b>{increment}</b>
        <button onClick={() => {
          setIncrement(i => i + 1);
        }}>+</button>
      </p>
    </>
  );
}

这样每次定时器触发时,getLatestValues都会拿到当前最新的count和increment,因为useEffectEvent会自动绑定最新的组件状态,而effect本身不需要添加任何依赖。

备注:内容来源于stack exchange,提问作者Vaibhav

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.22 12:44:37