You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

编写组件卸载后不触发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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 02:21:02