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

如何从映射对象推导Action类型以实现useReducer的dispatch类型安全?

基于Reducer动作映射自动推导Action类型

前言

我通常会采用以下方式定义reducer,以此保证dispatch方法的类型安全:

const setWhatever = (state: State, payload: Partial<State>) => ({ ...state, ...payload });

// 动作类型与处理函数的映射表
const actions = {
  SET_WHATEVER: setWhatever,
}

type Action =
  | { type: 'SET_WHATEVER', payload: Parameters<typeof setWhatever>[1] }

const reducer = (state: State, action: Action) => actions[action.type](state, action.payload);

const [state, dispatch] = useReducer(reducer, {});

这里type Action的定义重复且繁琐,能否基于const actions自动推导出type Action?

当前v1解决方案

我已经实现了一个基本可用的CreateActions<T>映射类型:

type CreateActions<T extends typeof actions> = {
  [K in keyof T]: {
    type: K;
    payload: T[K] extends (...args: any[]) => any ? Parameters<T[K]>[1] : never;
  };
}[keyof T];

type Action = CreateActions<typeof actions>;

但该方案仍存在两个问题:

  • reducer需要额外添加as never类型断言才能消除lint错误:
/**
 * 下方的`as never`用于解决如下类型错误:
 * ```
 * Argument of type 'string | number | Partial<{ name: string; age: number; }> | undefined' is not assignable to parameter of type 'never'.
 *  Type 'undefined' is not assignable to type 'never'.ts(2345)
 * ```
 * 
 * 问题:有没有更好的解决方式?
 */
const reducer = (state: State, action: Action): State =>
  actions[action.type](state, action.payload as never);
  • 所有dispatch调用都必须携带payload,能否让部分动作无需传入payload?
dispatch({ type: 'RESET', payload: undefined }); // 若写成dispatch({ type: 'RESET' })会触发lint报错

当前v2解决方案

感谢@ryskajakub提供的最终方案:

type Payload<T> = T extends (...args: any[]) => any
  ? Parameters<T>[1]
  : never;

type CreateActions<T extends typeof actions> = {
  [K in keyof T]: Payload<T[K]> extends undefined
    ? {
        type: K;
        // 定义可选payload避免reducer报错
        payload?: undefined;
      }
    : {
        type: K;
        payload: Payload<T[K]>;
      };
}[keyof T];

/** 鼠标悬停在Action上可查看判别联合类型 */
type Action = CreateActions<typeof actions>;

const reducer: ActionHandler<Action> = (state, action): State =>
  (actions[action.type] as ActionHandler<typeof action["payload"]>)(
    state,
    action.payload
  );

现在我可以无类型错误地调用dispatch({ type: 'RESET' })。
一个小问题是调用dispatch({ type: 'RESET', payload: undefined })也不会触发类型错误,但我对此并不在意。


内容的提问来源于Stack Exchange,提问作者David Horm

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 00:45:30