React函数组件调用第三方库回调时状态重置问题及解决方法
问题原因
这是函数组件的闭包特性导致的。你的useEffect依赖是空数组[],只会在组件首次渲染时执行一次,把当时的onChange函数传给了someLib。这个onChange捕获的是首次渲染时的test值(也就是初始值1),后续组件重新渲染时,虽然会生成新的onChange函数,但someLib里绑定的还是第一次的那个旧函数——所以当外部调用这个旧回调时,里面的test永远是1,执行setTest(test+1)就只会得到2,看起来像是被重置了。
而类组件里的方法是挂载在实例上的,每次调用时都会通过this.state拿到最新的状态值,不会有闭包捕获旧值的问题。
解决方法
方法1:使用函数式更新(推荐,最简单)
setState支持传入一个函数,这个函数会接收当前最新的状态值,返回新状态。这样不管闭包里捕获的是旧值还是新值,都能基于最新状态更新:
const onChange = (event) => { setTest(prevTest => prevTest + 1); }
方法2:用useRef保存最新的onChange引用
如果需要在回调里访问更多当前的props或state,除了函数式更新,还可以用useRef保存最新的函数引用,让someLib调用的始终是最新的版本:
const MyInput = (props) => { const { someLib, ...otherProps } = props; const [test, setTest] = useState(1); const onChangeRef = useRef(); // 每次渲染都更新ref,保存最新的onChange函数 onChangeRef.current = (event) => { setTest(test + 1); // 这里可以访问最新的props和state }; useEffect(() => { const callback = (event) => onChangeRef.current(event); someLib && someLib.addCallback(callback); // 记得清理回调,避免内存泄漏 return () => { someLib && someLib.removeCallback(callback); }; }, [someLib]); // 依赖加上someLib,确保someLib变化时重新绑定 }
方法3:用useCallback配合useEffect的依赖
如果someLib支持移除旧回调,可以用useCallback包裹onChange,并把它加入useEffect的依赖,这样每次test变化时,都会重新绑定最新的回调:
const MyInput = (props) => { const { someLib, ...otherProps } = props; const [test, setTest] = useState(1); const onChange = useCallback((event) => { setTest(test + 1); }, [test]); // 依赖test,test变化时生成新的onChange useEffect(() => { someLib && someLib.addCallback(onChange); // 清理旧回调 return () => { someLib && someLib.removeCallback(onChange); }; }, [someLib, onChange]); // 依赖加上onChange,变化时重新绑定 }
不过这种方法每次test变化都会触发回调的解绑和重新绑定,如果someLib对频繁绑定解绑敏感,建议优先用方法1或2。
内容的提问来源于stack exchange,提问作者John Taylor
相关产品推荐
相关产品推荐

