TypeScript React中useEffect检测state非空但callback内state恒为null问题
问题成因
这是典型的React闭包陷阱问题:
- 父组件首次渲染时,创建的
callback函数捕获了当前作用域下的_object值,也就是初始值null - 子组件的
useLayoutEffect依赖项为空数组,仅会在组件首次挂载时执行一次,此时绑定的terminal.onData回调就是父组件首次渲染生成的旧callback - 后续父组件
_object状态更新触发重渲染,会生成新的callback函数,新函数捕获的是更新后的_object值,但子组件没有监听callback的变化,不会重新绑定事件,始终使用旧的闭包函数,因此永远读取到_object为null
解决方法
提供两种可行方案,按需选择:
方案1:子组件添加callback依赖,更新事件绑定
修改子组件的useLayoutEffect逻辑,新增callback作为依赖项,同时添加事件解绑逻辑避免内存泄漏:
const Child: React.FunctionComponent<ChildProps> = (props) => { const { callback, returnTerminal } = props; const ref = useRef<HTMLDivElement>(null); const terminal = new Terminal(); useLayoutEffect(() => { let disposable: { dispose: () => void } | undefined; // 每次callback更新时重新绑定事件 if (callback) { disposable = terminal.onData(callback); } if (ref.current) terminal.open(ref.current); returnTerminal(terminal); // 销毁时解绑旧事件 return () => { disposable?.dispose(); }; }, [callback, returnTerminal]); // 新增依赖 return <div ref={ref}></div>; }; export default Child;
同时父组件的returnTerminal和callback建议用useCallback包裹,避免不必要的重绑定:
const Parent: React.FunctionComponent<ParentProps> = () => { const [_object, set_object] = useState<Terminal>(null); // 用useCallback包裹,依赖为空引用稳定 const handleGetTerminal = useCallback((terminal: Terminal) => { set_object(terminal); }, []); useEffect(() => { if (_object !== null) { _object.writeln("_object is not null"); } }, [_object]); // 用useCallback包裹,依赖_object,状态更新时才生成新函数 const callback = useCallback(() => { console.log(_object === null); }, [_object]); return ( <Child returnTerminal={handleGetTerminal} callback={callback} /> ); };
方案2:用useRef存储状态规避闭包问题
如果不需要频繁响应_object的更新渲染,可直接用ref存储terminal实例,从根源避免闭包捕获问题:
const Parent: React.FunctionComponent<ParentProps> = () => { // 用ref存储实例,不需要触发渲染的话可以不用useState const objectRef = useRef<Terminal>(null); const handleGetTerminal = (terminal: Terminal) => { objectRef.current = terminal; // 如果需要触发渲染可以保留useState // set_object(terminal); }; const callback = () => { // 直接读取ref的current属性,永远是最新值 console.log(objectRef.current === null); }; // 其余逻辑不变 }
内容的提问来源于stack exchange,提问作者Aman Jha
相关产品推荐
相关产品推荐

