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

React中useCallback缓存的回调被Set重复添加的原因及解决方法

React StrictMode下useCallback生成的函数被Set重复添加的原因及解决方法

原因分析

你遇到的问题完全是React StrictMode在开发环境下的特性导致的:

React 18的StrictMode为帮助开发者检测未正确清理的副作用,会在开发环境中模拟组件的挂载-卸载-重新挂载流程:

  1. 第一次执行组件函数(预备挂载):useCallback生成函数实例A,testSet.add(A)将其加入集合;
  2. React立即模拟卸载这个预备实例;
  3. 第二次执行组件函数(真正挂载):useCallback生成另一个函数实例B(新挂载周期的缓存不共享),testSet.add(B)再次加入集合。

由于A和B是不同的函数引用,Set会将它们视为独立元素,因此集合中会出现两个逻辑相同但引用不同的函数。组件状态更新属于同一挂载周期内的重渲染,useCallback会返回缓存的B,不会再添加新元素。关闭StrictMode后,双重挂载流程被跳过,组件仅执行一次挂载,自然不会出现重复问题。

解决方案

问题核心是你在组件函数顶层直接执行了testSet.add(callback)这类未被React追踪的副作用。正确做法是将操作放到useEffect中,并配合清理函数,让React管理副作用生命周期:

修改后的App.tsx代码:

import { useCallback, useState, useEffect } from "react";
import { testSet } from "./test.ts";

export default function App() {
  const callback = useCallback(() => {}, []);
  const [state, setState] = useState(0);

  useEffect(() => {
    // 挂载时添加回调
    testSet.add(callback);
    // 卸载时移除回调,避免内存泄漏和重复添加
    return () => {
      testSet.delete(callback);
    };
  }, [callback]); // 依赖数组包含callback,确保回调更新时同步操作Set

  console.log(testSet);
  return (
    <div className="App">
      {state}
      <button
        onClick={() => {
          setState((prev) => prev + 1);
        }}
      >
        Update state
      </button>
    </div>
  );
}

这样处理后:

  • StrictMode下第一次挂载添加A,卸载时移除A;第二次挂载添加B,最终Set仅保留B;
  • 组件状态更新时,callback依赖为空数组不会重新生成,useEffect不会重复执行;
  • 组件真正卸载时,清理函数自动移除回调,避免内存泄漏。

额外说明

若需求是全局仅添加一次该回调,可进一步优化:比如用useRef标记是否已添加,或在test.ts中封装添加逻辑(通过自定义标识而非引用判断函数唯一性),但更推荐遵循React副作用管理规范,用useEffect配合清理函数处理组件生命周期绑定的操作。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 22:07:03