useCallback依赖待更新state触发无限循环该如何解决?
问题根因
无限循环的根因是useCallback依赖了整个panels状态对象,每次panels更新都会生成新的onValidatePanel实例,若该回调被绑定到自动触发的执行链路(比如useEffect依赖、组件的响应式属性),就会重复执行修改状态的逻辑,形成死循环。
最优解决方案
利用useImmer(原生useState的函数式更新也有相同特性)的更新回调特性:传入updatePanels的函数参数可以拿到最新的状态副本,不需要在useCallback外层读取panels状态,直接把判断逻辑挪到更新回调内即可。这样可以完全移除对panels的依赖,updatePanels本身是稳定引用不会变化,onValidatePanel只会在组件初始化时生成一次:
const [panels, updatePanels] = useImmer({ activePanel: 0, validPanels: [], }); const onValidatePanel = useCallback(isValid => { updatePanels(draft => { const panelIndex = draft.validPanels.indexOf(draft.activePanel); // 新增 if (panelIndex === -1 && isValid) { draft.validPanels.push(draft.activePanel); // 移除 } else if (panelIndex > -1 && !isValid) { draft.validPanels.splice(panelIndex, 1); } }); }, [updatePanels]);
备选方案
如果业务逻辑确实需要在更新逻辑外读取状态,可将大状态拆分为更小的独立状态,仅将需要用到的变量作为useCallback的依赖,避免依赖整个大状态对象导致回调频繁刷新:
// 拆分状态,减少依赖范围 const [activePanel, setActivePanel] = useState(0); const [validPanels, updateValidPanels] = useImmer([]); const onValidatePanel = useCallback(isValid => { const panelIndex = validPanels.indexOf(activePanel); if (panelIndex === -1 && isValid) { updateValidPanels(draft => { draft.push(activePanel); }); } else if (panelIndex > -1 && !isValid) { updateValidPanels(draft => { draft.splice(panelIndex, 1); }); } }, [activePanel, validPanels, updateValidPanels]);
内容的提问来源于stack exchange,提问作者Brandon Durham
相关产品推荐
相关产品推荐

