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

如何在React Reducer中根据action.type映射对应state键的value类型?

问题描述

给定初始状态:

const initialState = {
  hasError: false,
  counter: 0,
  token: [...Array(2)].map(() => ''),
};

以及类似React官方示例的reducer结构(修改为使用action.value而非依赖当前状态值):

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: action.value + 1};
    case 'decrement':
      return {count: action.value - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement', value: 1})}>-</button>
      <button onClick={() => dispatch({type: 'increment', value: -1})}>+</button>
    </>
  );
}

需要实现的是:让reducer根据action.type自动推导action.value的类型。比如处理'hasError'类型的action时,action.value应自动推导为boolean;处理'token'类型时,action.value自动推导为string[]。

用户尝试过以下两种写法,但均未达到预期效果:

第一种写法:

const reducer = <K extends keyof typeof initialState>(state: typeof initialState, action: { type: K; value: typeof initialState[K] }): typeof initialState => {
  switch (action.type) {
    case 'hasError': {
      return {
        ...state,
        hasError: action.value, // 期望此处类型为`boolean`,但未正确推导
      };
    }
    case 'token': {
    return {
      ...state,
      token: action.value, // 期望此处类型为`string[]`,但未正确推导
    }
   // 其他情况...
  }
};

第二种写法:

type TypeResolver<K> = Extract<TypeMap[keyof TypeMap], { state: K }>['valueType'];

type TypeObject<K extends keyof typeof initialState> = { state: K, valueType: typeof initialState[K] };

type TypeMap = {
  error: TypeObject<'hasError'>;
  resend: TypeObject<'resendEnabled'>;
  timerIteration: TypeObject<'timerIteration'>;
};

const reducer = <K extends keyof TypeMap>(state: typeof initialState, action: { type: K; value: TypeResolver<K> }): typeof initialState => {
  //...
}
解决方案

核心思路是创建包含所有合法action的联合类型,让TypeScript通过switch语句的类型收窄自动推导对应value的类型,替代泛型方案。

步骤1:定义Action联合类型

基于initialState的键,自动生成每个键对应的action类型,再组合成联合类型:

type State = typeof initialState;
type Action = {
  [K in keyof State]: { type: K; value: State[K] }
}[keyof State];

这段代码会自动生成如下联合类型:

type Action = 
  | { type: 'hasError'; value: boolean }
  | { type: 'counter'; value: number }
  | { type: 'token'; value: string[] };

步骤2:编写带类型推导的reducer

将reducer的action参数指定为Action类型,此时在switch的每个case分支里,TypeScript会自动收窄action的类型,从而准确推导action.value的具体类型:

const reducer = (state: State, action: Action): State => {
  switch (action.type) {
    case 'hasError': {
      return {
        ...state,
        hasError: action.value, // 自动推导为boolean类型
      };
    }
    case 'counter': {
      return {
        ...state,
        counter: action.value, // 自动推导为number类型
      };
    }
    case 'token': {
      return {
        ...state,
        token: action.value, // 自动推导为string[]类型
      };
    }
    default:
      // 兜底检查,确保覆盖所有action类型
      const _exhaustiveCheck: never = action;
      throw new Error(`未知action类型: ${_exhaustiveCheck.type}`);
  }
};

原泛型写法失效原因

之前的泛型写法K extends keyof State会让TypeScript将K识别为所有键的联合类型,而非单个具体键,因此在switch分支里无法进行类型收窄,导致action.value始终是State[keyof State](即boolean | number | string[]),无法自动推导为对应分支的具体类型。而使用联合类型的Action,每个case分支会明确匹配联合类型中的某一个子类型,TypeScript就能准确完成类型收窄。

内容的提问来源于stack exchange,提问作者Rick Stanley

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 16:35:18