React回调传入TypeScript单例后setState失效问题排查
问题根源:单例持有的是旧回调的闭包
Daemon是全局单例,一旦你把updateTime传给getInstance,它就会一直攥着这个回调的引用不放。但React组件每次渲染(不管是状态变化还是props更新)都会重新生成updateTime函数,单例里存的却始终是第一次传进去的旧版本。
这个旧的updateTime通过闭包捕获的是组件第一次渲染时的updateSimTime和状态上下文,等组件后续重新渲染后,这个旧回调里的updateSimTime根本管不了当前组件的状态——它只认当初那个渲染周期的状态,自然触发不了更新。你可以试试在updateTime里打印当前的simTime,肯定显示的是初始值,这就是闭包在搞鬼。
解决办法
办法1:用useCallback+useEffect同步单例回调
把updateTime用useCallback包裹,确保只有updateSimTime变化时才重新生成函数,然后每次组件渲染都把新的回调同步给单例:
const updateTime = useCallback((time: string) => { updateSimTime(time); console.log(time); }, [updateSimTime]); useEffect(() => { const daemon = Daemon.getInstance(updateTime); // 组件卸载时可清理回调,避免内存泄漏(如果Daemon有对应方法的话) return () => { // daemon.clearCallback(); }; }, [updateTime]);
办法2:给Daemon添加更新回调的方法
修改Daemon类,让它支持动态替换回调,这样组件能主动把新的updateTime传给它:
// Daemon.ts class Daemon { private static instance: Daemon; private callback: (time: string) => void; private constructor(callback: (time: string) => void) { this.callback = callback; setInterval(() => this.tick(), 1000); } public static getInstance(callback?: (time: string) => void): Daemon { if (!Daemon.instance) { if (!callback) throw new Error("首次初始化必须传回调"); Daemon.instance = new Daemon(callback); } return Daemon.instance; } // 新增:动态更新回调的方法 public updateCallback(newCallback: (time: string) => void): void { this.callback = newCallback; } private tick(): void { const currentTime = new Date().toISOString(); this.callback(currentTime); } }
然后在组件中同步最新回调:
const updateTime = useCallback((time: string) => { updateSimTime(time); console.log(time); }, [updateSimTime]); useEffect(() => { const daemon = Daemon.getInstance(); daemon.updateCallback(updateTime); }, [updateTime]);
办法3:用useRef存储最新的setter
把updateSimTime放进useRef里,让旧回调也能访问到最新的状态设置函数:
const updateSimTimeRef = useRef<typeof updateSimTime>(updateSimTime); // 每次updateSimTime更新时同步到ref useEffect(() => { updateSimTimeRef.current = updateSimTime; }, [updateSimTime]); const updateTime = useCallback((time: string) => { updateSimTimeRef.current(time); console.log(time); }, []); // 初始化单例 useEffect(() => { Daemon.getInstance(updateTime); }, [updateTime]);
内容的提问来源于stack exchange,提问作者Null Salad
相关产品推荐
相关产品推荐

