React中useCallback缓存的回调被Set重复添加的原因及解决方法
React StrictMode下useCallback生成的函数被Set重复添加的原因及解决方法
原因分析
你遇到的问题完全是React StrictMode在开发环境下的特性导致的:
React 18的StrictMode为帮助开发者检测未正确清理的副作用,会在开发环境中模拟组件的挂载-卸载-重新挂载流程:
- 第一次执行组件函数(预备挂载):
useCallback生成函数实例A,testSet.add(A)将其加入集合; - React立即模拟卸载这个预备实例;
- 第二次执行组件函数(真正挂载):
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
相关产品推荐
相关产品推荐

