如何移除React状态dispatch更新中的函数依赖?解决无限循环问题
我太懂你这个头疼的无限循环了——咱们先把这个死循环的链条理清楚:foo状态更新 → handleBar因为依赖foo重新创建 → 依赖handleBar的useEffect触发 → 调用handleBar又更新foo → 周而复始停不下来。核心问题就是handleBar对foo的依赖,导致它每次foo变化都要重新生成,进而触发依赖它的useEffect。
下面给你几个可行的解决方案,咱们一个个看:
方案一:把判断逻辑移到Reducer中(最推荐)
既然handleBar里的核心逻辑是先判断bar是否在foo里,再决定dispatch什么action,那咱们完全可以把这个判断逻辑搬到reducer内部去做。这样handleBar就不需要依赖foo了,只需要传递bar就行。
修改后的代码大概是这样:
const { state, dispatch } = React.useContext(AppContext); // 现在handleBar只依赖dispatch,而dispatch通常是稳定的(不会随组件渲染频繁变化) const handleBar = React.useCallback((bar: IBarType) => { // 直接把bar传给reducer,让reducer去做判断逻辑 dispatch({ type: 'PROCESS_BAR', payload: bar }); }, [dispatch]); // 对应的AppContext reducer里添加处理逻辑 function appReducer(state, action) { switch (action.type) { case 'PROCESS_BAR': { const bar = action.payload; const bar_exists = _.find(state.foo, { id: bar.id }); if (bar_exists) { // 原来的rainbows更新逻辑 return { ...state, foo: /* 你的更新逻辑 */ }; } else { // 原来的clouds更新逻辑 return { ...state, foo: /* 你的更新逻辑 */ }; } } // 其他原有case... } }
这样一来,handleBar的依赖只有稳定的dispatch,不会再随着foo变化而重新创建,useEffect也就不会反复触发了,完美打破循环。
方案二:用useRef保存最新的foo
如果你不想改动reducer的逻辑,可以用useRef来实时同步最新的foo状态,这样handleBar就不需要依赖foo,而是直接读取ref的当前值:
const { state, dispatch } = React.useContext(AppContext); const { foo } = state; const fooRef = React.useRef(foo); // 每次foo更新时,同步更新ref的current值 React.useEffect(() => { fooRef.current = foo; }, [foo]); // 现在handleBar的依赖只需要dispatch,因为它用的是实时的fooRef.current const handleBar = React.useCallback((bar: IBarType) => { const bar_exists = _.find(fooRef.current, { id: bar.id }); if (bar_exists) { dispatch({ type: 'rainbows', payload: bar }); } else { dispatch({ type: 'clouds', payload: bar }); } }, [dispatch]); React.useEffect(() => { // 你的serverFunction逻辑 if (x) { handleBar(data); } }, [handleBar]);
这个方法的原理是,fooRef会始终保存最新的foo值,而handleBar因为不依赖foo,所以不会频繁重新创建,自然就打破了循环链。
方案三:调整useEffect的依赖项
如果你的useEffect逻辑不算复杂,可以直接把handleBar里的逻辑搬到useEffect内部,让useEffect依赖实际需要的变量(比如foo、dispatch、x),而不是依赖handleBar:
const { state, dispatch } = React.useContext(AppContext); const { foo } = state; React.useEffect(() => { // 你的serverFunction逻辑 if (x) { // 直接在这里处理判断和dispatch,不用调用handleBar const bar_exists = _.find(foo, { id: data.id }); if (bar_exists) { dispatch({ type: 'rainbows', payload: data }); } else { dispatch({ type: 'clouds', payload: data }); } } }, [foo, dispatch, x]); // 依赖实际需要的变量
这种方式比较直接,但如果逻辑复杂的话会让useEffect代码变臃肿,适合简单场景。
最后再啰嗦一句:React Hook的依赖一定要准确,但有时候我们可以通过逻辑转移或者ref来避免不必要的依赖更新,从而打破这种恼人的循环~
内容的提问来源于stack exchange,提问作者Xavier

