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

React中useEffect内事件处理器无法更新state问题求助

问题原因

你的代码存在两个核心问题:

  1. 闭包陷阱:由于useEffect的依赖数组为空,handleProgress函数只会在组件首次挂载时创建一次。虽然setLoading本身是React稳定提供的函数,但如果事件触发时需要依赖前一次状态更新(比如累加进度),闭包会导致无法获取最新状态;即使你当前是直接替换状态对象,也可能因React的状态更新机制出现预期外的延迟。
  2. 事件绑定目标的合理性:原生progress事件通常属于XMLHttpRequest、Fetch请求对象或媒体元素(如<audio>),document对象默认不会触发该事件。你提到console.log(e.loaded)有输出,说明事件确实被捕获,但绑定到document并非最优选择,容易出现事件丢失或延迟的情况。

解决方案

方案1:使用函数式更新规避闭包问题

通过函数式更新获取前一次状态,确保每次状态更新都基于最新值:

export default function MyProgress() {
  const [loading, setLoading] = useState({ message: "", progress: 0 });

  useEffect(() => {
    function handleProgress(e: ProgressEvent<EventTarget>) {
      console.log(e.loaded);
      // 函数式更新,接收前一次状态作为参数
      setLoading(prev => ({
        ...prev,
        message: "Processing Data",
        progress: e.loaded
      }));
    }

    document.addEventListener("progress", handleProgress);
    return () => {
      document.removeEventListener("progress", handleProgress);
    };
  }, []);

  return (
    <div>
      <p>{loading.message}</p>
      <Progress value={loading.progress} />
    </div>
  )
}

方案2:绑定到正确的事件触发目标

如果progress事件来自请求或媒体对象,直接绑定到对应对象上更可靠,以XHR请求为例:

export default function MyProgress() {
  const [loading, setLoading] = useState({ message: "", progress: 0 });

  useEffect(() => {
    function handleProgress(e: ProgressEvent<XMLHttpRequest>) {
      console.log(e.loaded);
      // 若需要百分比进度,计算方式如下
      const progressPercent = Math.round((e.loaded / e.total) * 100);
      setLoading({
        message: "Processing Data",
        progress: progressPercent
      });
    }

    // 初始化XHR请求并绑定事件
    const xhr = new XMLHttpRequest();
    xhr.open('GET', '/your-api-url');
    xhr.addEventListener('progress', handleProgress);
    xhr.send();

    // 清理函数:移除监听器并终止请求
    return () => {
      xhr.removeEventListener('progress', handleProgress);
      xhr.abort();
    };
  }, []);

  return (
    <div>
      <p>{loading.message}</p>
      <Progress value={loading.progress} />
    </div>
  )
}

方案3:用useCallback稳定事件处理函数

如果后续需要添加其他依赖,可通过useCallback保持函数引用稳定,避免不必要的监听解绑/绑定:

export default function MyProgress() {
  const [loading, setLoading] = useState({ message: "", progress: 0 });

  // 用useCallback稳定函数引用
  const handleProgress = useCallback((e: ProgressEvent<EventTarget>) => {
    console.log(e.loaded);
    setLoading({
      message: "Processing Data",
      progress: e.loaded
    });
  }, [setLoading]);

  useEffect(() => {
    document.addEventListener("progress", handleProgress);
    return () => {
      document.removeEventListener("progress", handleProgress);
    };
  }, [handleProgress]);

  return (
    <div>
      <p>{loading.message}</p>
      <Progress value={loading.progress} />
    </div>
  )
}

内容的提问来源于stack exchange,提问作者Milan Valáš

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 20:39:49