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

如何收窄TypeScript中重映射后的类型?

问题:TypeScript中Action类型收窄失败,response仍为联合类型

我尝试收窄以下代码中response属性的类型,但它最终仍是联合类型{ users: string[]; } | { user: string; },请问问题出在哪?

type ActionMap = {
  list: {
    response: { users: string[] };
    error?: number | null;
  };
  create: {
    response: { user: string };
  };
};

// Remapping keys to add the start, finish and failure suffixes to each action
type ActionMapWithStages<T> = {
  [Property in keyof T as
    | `${string & Property}-start`
    | `${string & Property}-finish`
    | `${string & Property}-failure`]: T[Property];
};

type Action<T> = ActionMapWithStages<T> extends infer U
  ? {
      type: keyof U;
      payload: U[keyof U];
    }
  : never;


let action: Action<ActionMap> = { type: 'list-start', payload: { response: { users: [] } } };

if (action.type === 'list-start') {
  console.log(action.payload.response.users) // Property 'users' does not exist on type '{ users: string[]; } | { user: string; }'.
}

问题原因

你当前的Action<T>类型定义破坏了type和payload的关联关系:

  • { type: keyof U; payload: U[keyof U]; }这种写法会生成一个单一类型,其中type是所有action类型的联合,payload是所有payload类型的联合。
  • TypeScript无法推断出某个特定的type值对应哪个具体的payload类型,所以即使你用action.type === 'list-start'做类型守卫,payload依然是所有可能payload的联合类型,导致response无法被正确收窄。

解决方案

需要将Action<T>定义为可辨识联合类型,让每个type和对应的payload一一绑定。修改Action<T>的实现如下:

type ActionMap = {
  list: {
    response: { users: string[] };
    error?: number | null;
  };
  create: {
    response: { user: string };
  };
};

// Remapping keys to add the start, finish and failure suffixes to each action
type ActionMapWithStages<T> = {
  [Property in keyof T as
    | `${string & Property}-start`
    | `${string & Property}-finish`
    | `${string & Property}-failure`]: T[Property];
};

// 生成可辨识联合类型,每个type对应唯一的payload
type Action<T> = {
  [K in keyof ActionMapWithStages<T>]: {
    type: K;
    payload: ActionMapWithStages<T>[K];
  };
}[keyof ActionMapWithStages<T>];


let action: Action<ActionMap> = { type: 'list-start', payload: { response: { users: [] } } };

if (action.type === 'list-start') {
  console.log(action.payload.response.users); // 类型收窄成功,可正常访问users
}

原理说明

  1. 通过映射ActionMapWithStages<T>的每个键K,为每个action类型生成独立的{ type: K; payload: ... }类型,确保type和payload严格绑定。
  2. 最后通过[keyof ActionMapWithStages<T>]将所有独立的action类型合并成联合类型。
  3. 此时TypeScript能识别type作为联合类型的辨识符,当你通过action.type === 'list-start'过滤时,会自动将action.payload收窄为对应的list类型,response的类型也就明确为{ users: string[] }了。

内容的提问来源于stack exchange,提问作者Bruno Correia

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 21:24:58