React自定义hook回调仅能访问初始state问题求解
问题根因
这是典型的React Hooks闭包陷阱:
- 首次渲染时创建的
ensureMounted和play函数,捕获了初始的state值(mounted: false) - 后续
mounted状态更新触发hook重渲染时,虽然会生成新的函数版本,但只要调用方持有的是旧版本的play函数引用,就永远只能读到初始化时的state,无法拿到最新值。
解决方案
方案1:用Ref存最新状态(推荐,适配状态频繁更新场景)
Ref的current属性永远指向最新值,不受闭包限制,不用依赖函数引用更新就能读到最新状态:
const usePlayer = () => { const audioRef = useRef(__GLOBAL_AUDIO); const [state, setState] = useReducer((state, newState) => ({...state, ...newState}), { mounted: false, playing: false, }); // 新增ref同步最新state const latestStateRef = useRef(state); // state变化时自动同步到ref useEffect(() => { latestStateRef.current = state; }, [state]); const ensureMounted = callback => { // 从ref读取最新状态,不会被闭包卡住 if(latestStateRef.current.mounted){ callback(); return; } audioRef.current.src = cdn("/link/to/new/src.mp3"); onLoad(callback); }; const play = () => ensureMounted(() => { audioRef.current.play(); }); useEffect(() => { const _mounted = checkToSeeIfMounted(); setState({ mounted: _mounted }); }, deps); return { playing: state.playing, play: play, }; };
方案2:用useCallback补全依赖
如果调用方可以正确接收更新后的函数引用,可以用useCallback显式声明依赖,每次状态变化后返回新的函数版本:
// ensureMounted依赖state,state变化时生成新函数 const ensureMounted = useCallback(callback => { if(state.mounted){ callback(); return; } audioRef.current.src = cdn("/link/to/new/src.mp3"); onLoad(callback); }, [state]); // play依赖ensureMounted,同步更新 const play = useCallback(() => ensureMounted(() => { audioRef.current.play(); }), [ensureMounted]);
注意使用这个方案时,调用方如果用到useCallback、useMemo或者React.memo,必须把play加入依赖数组,否则还是会持有旧的函数引用。
内容的提问来源于stack exchange,提问作者Grover
相关产品推荐
相关产品推荐

