编写组件卸载后不触发dispatch的自定义useReducer 求代码反馈
自定义useUnmountSafeReducer实现反馈
现有实现存在的核心问题
- 逻辑完全不生效:你当前是在渲染阶段根据
mounted状态返回不同的dispatch函数,但mounted是组件state,组件卸载后不会触发重新渲染,你返回给用户的永远是卸载前那次渲染对应的原生dispatch,卸载后调用依然会执行,完全达不到拦截效果 - 类型覆盖不全:只支持了useReducer的两参数重载,没有覆盖带
initializer初始化函数的三参数用法,和原生useReducer的API不兼容 - 存在额外无效渲染:组件卸载时调用
setMounted(false)会触发一次不必要的重渲染,属于冗余操作
优化后实现方案
正确的思路是用useRef存储挂载状态(不需要触发渲染,取值实时),对dispatch做一层封装,每次调用时判断当前是否已卸载,再决定是否执行原生dispatch:
import { Reducer, ReducerState, ReducerAction, Dispatch, useRef, useReducer, useEffect } from 'react'; // 重载1:无initializer的两参数用法 export function useUnmountSafeReducer<R extends Reducer<any, any>>( reducer: R, initialState: ReducerState<R> ): [ReducerState<R>, Dispatch<ReducerAction<R>>]; // 重载2:带initializer的三参数用法 export function useUnmountSafeReducer<R extends Reducer<any, any>, I>( reducer: R, initialArg: I, initializer: (arg: I) => ReducerState<R> ): [ReducerState<R>, Dispatch<ReducerAction<R>>]; export function useUnmountSafeReducer<R extends Reducer<any, any>, I>( reducer: R, initialArg: I, initializer?: (arg: I) => ReducerState<R> ): [ReducerState<R>, Dispatch<ReducerAction<R>>] { const mountedRef = useRef(true); const [state, rawDispatch] = useReducer(reducer, initialArg, initializer as any); useEffect(() => { return () => { mountedRef.current = false; }; }, []); // 封装dispatch,调用时实时判断挂载状态 const safeDispatch: Dispatch<ReducerAction<R>> = (action) => { if (mountedRef.current) { rawDispatch(action); } }; return [state, safeDispatch]; }
优化点说明
- 用
useRef存储挂载状态,取值是实时的,不会依赖组件渲染更新,卸载时修改ref也不会触发额外渲染 - 调用dispatch时才判断挂载状态,不管是组件卸载前还是卸载后触发的异步dispatch,都能正确拦截
- 补全了和原生useReducer完全对齐的类型重载,所有原生useReducer的用法都可以无缝替换为这个自定义Hook
- 封装后的safeDispatch类型和原生Dispatch完全兼容,没有类型丢失问题
内容的提问来源于stack exchange,提问作者Mykyta
相关产品推荐
相关产品推荐

