如何收窄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 }
原理说明
- 通过映射
ActionMapWithStages<T>的每个键K,为每个action类型生成独立的{ type: K; payload: ... }类型,确保type和payload严格绑定。 - 最后通过
[keyof ActionMapWithStages<T>]将所有独立的action类型合并成联合类型。 - 此时TypeScript能识别
type作为联合类型的辨识符,当你通过action.type === 'list-start'过滤时,会自动将action.payload收窄为对应的list类型,response的类型也就明确为{ users: string[] }了。
内容的提问来源于stack exchange,提问作者Bruno Correia
相关产品推荐
相关产品推荐

