React自定义UNSAFE_useDatepicker Hook重渲染未更新问题排查
两种方案差异的原因
核心问题出在闭包捕获和Hook内部状态的更新逻辑上:
依赖
valuesInitialized调用setSelected的情况
当父组件的valuesInitialized状态切换时,useEffect触发,此时直接将当前最新的props.defaultSelected传入setSelected。这里setSelected接收的是当前渲染周期的最新defaultSelected值,自然能正确更新Hook内部的selected状态。依赖
initialValue调用reset的情况
假设你的UNSAFE_useDatepicker内部的reset函数是这样实现的:const reset = () => { setSelected(initialValue); };这个
reset函数会捕获Hook初始化时的initialValue闭包值。当props.defaultSelected从undefined变为API返回值时,虽然UNSAFE_useDatepicker会重新执行,新的initialValue是有效日期,但之前创建的reset函数引用的还是旧的initialValue(undefined)。因此当useEffect依赖initialValue变化触发时,调用的reset依然会把selected设置为undefined,导致状态不更新。
仅通过值作为依赖的正确实现方式
要解决这个问题,需要确保reset函数能获取到最新的initialValue,可以通过以下两种方式修改自定义Hook:
方式1:用useRef保存最新的initialValue
在Hook内部用ref跟踪initialValue的最新值,让reset始终使用当前最新的值:
function UNSAFE_useDatepicker(initialValue) { const [selected, setSelected] = useState(initialValue); const latestInitialValue = useRef(initialValue); // 同步最新的initialValue到ref useEffect(() => { latestInitialValue.current = initialValue; }, [initialValue]); const reset = useCallback(() => { setSelected(latestInitialValue.current); }, []); return { selected, setSelected, reset }; }
然后在DatePickerInput中:
function DatePickerInput({ defaultSelected }) { const { selected, reset } = UNSAFE_useDatepicker(defaultSelected); useEffect(() => { // 仅当defaultSelected有值时触发重置 if (defaultSelected !== undefined) { reset(); } }, [defaultSelected, reset]); // 依赖reset(useCallback包裹后引用稳定) // 渲染逻辑 return <div>{selected}</div>; }
方式2:让reset接收参数,直接传入最新值
简化reset函数,让它接收一个可选值,调用时直接传入当前的defaultSelected:
function UNSAFE_useDatepicker(initialValue) { const [selected, setSelected] = useState(initialValue); const reset = useCallback((value) => { setSelected(value ?? initialValue); }, [initialValue]); return { selected, setSelected, reset }; }
在DatePickerInput中:
function DatePickerInput({ defaultSelected }) { const { selected, reset } = UNSAFE_useDatepicker(defaultSelected); useEffect(() => { if (defaultSelected !== undefined) { reset(defaultSelected); } }, [defaultSelected, reset]); // 渲染逻辑 return <div>{selected}</div>; }
这两种方式都能确保当defaultSelected变化时,Hook内部的selected状态能正确更新,且只依赖defaultSelected这个值作为触发条件。
内容的提问来源于stack exchange,提问作者Leff

