React Context回调中调用setState仅半数情况更新状态问题求助
问题根因
核心是闭包陈旧值陷阱,你的onCloseDialog函数在定义时,捕获了当前渲染闭包中的dialogConfig状态值。当你在调用onOpenDialog时,将当前闭包中的onCloseDialog作为配置项存入dialogConfig状态后,这个被存储的onCloseDialog永远绑定的是存入时刻的旧dialogConfig值,而非最新的状态。
当你点击Cancel按钮调用这个被存储的onCloseDialog时,函数内部判断的是旧的dialogConfig长度:如果存入时dialogConfig是空对象(比如上一次关闭后重置的状态),判断条件Object.keys(dialogConfig).length > 0就不成立,自然不会执行清空dialogConfig的逻辑,这就是为什么出现50%概率失效的现象:第一次打开时存入的onCloseDialog绑定空对象,点击不会清空;第二次打开时存入的onCloseDialog绑定的是上一次未清空的有值对象,点击就会清空,来回交替就出现了偶发的情况。
修复方案
最简单的方案是移除不必要的状态判断,直接重置配置,多余的空值判断完全没有必要,就算当前dialogConfig已经是空对象,设置为空对象React也会自动跳过无效更新:
const onCloseDialog = () => { setDialogMode(""); setDialogConfig({}); }
如果确实需要保留判断逻辑,可以改用状态更新的函数式写法,直接读取最新的前序状态,规避闭包问题:
const onCloseDialog = () => { setDialogMode(""); setDialogConfig(prevConfig => { return Object.keys(prevConfig).length > 0 ? {} : prevConfig }) }
额外优化建议
你当前Context的value每次渲染都会生成新的普通对象,会导致所有消费该Context的组件不必要重渲染,可以优化为用useMemo包裹value,同时用useCallback包裹两个操作方法:
const onOpenDialog = useCallback((mode, config) => { setDialogMode(mode) if (config) { setDialogConfig(config); } }, []) const onCloseDialog = useCallback(() => { setDialogMode(""); setDialogConfig({}); }, []) const contextValue = useMemo(() => ({ dialogMode, dialogConfig, onOpenDialog, onCloseDialog }), [dialogMode, dialogConfig, onOpenDialog, onCloseDialog]) return ( <Provider value={contextValue}> {children} </Provider> )
内容的提问来源于stack exchange,提问作者eakl

