【问题标题】:How to discriminate a discriminated union type如何区分有区别的联合类型
【发布时间】:2017-02-01 00:03:13
【问题描述】:

假设我有一个可区分的联合类型来表示 Redux 操作:

interface AddTodoAction { type: 'ADD_TODO'; description: string; }
interface RemoveTodoAction { type: 'REMOVE_TODO'; id: number; }
type Action = AddTodoAction | RemoveTodoAction;

如果我想将动作类型映射到处理它们的化简器,我可能会从:

type ActionReducers = {
  [P in Action['type']]: (state: State, action: Action) => State
};

但是,第二个参数 (action: Action) 过于笼统。我想说“Actiontype对应P”,但不知道是否存在。我试过Action & {type: P},但结果恰恰相反。

有什么想法吗?

【问题讨论】:

  • 我认为最接近所需行为的方法是使用类型保护,这样的减速器编译时不会出错:(state: State, action: Action) => { switch (action.type) { case 'ADD_TODO': action.description; return state; case 'REMOVE_TODO': action.id; return state; } } hmpf 我错过了 cmets 中的真实代码格式

标签: typescript


【解决方案1】:

2018 年 7 月更新

自从我写下这个答案后,TypeScript 2.8 引入了conditional types,这使得这成为可能。

例如,在这种情况下:

type DiscriminateAction<T extends Action['type']> = Extract<Action, {type: T}>

其中Extract&lt;T, U&gt;conditional type from the standard library,定义为:

type Extract<T, U> = T extends U ? T : never;

它使用条件类型的distributive 属性来拆分联合T 并仅提取与U 匹配的部分。

ActionReducers 的定义如下:

type ActionReducers = {
  [P in Action['type']]: (state: State, action: DiscriminateAction<P>) => State
};

所以,这行得通!希望对人们有所帮助。


原始答案,2017 年 7 月

TypeScript 不允许您自动查找标记联合的类型。这是一个好主意,所以你可能想make a suggestion。该逻辑已作为控制流分析的一部分实现;也许它可以作为某种类型的运算符公开。


在没有此功能的情况下,有一些变通方法。最直接的方法是自己声明反向映射,然后在需要时引用它,代价是一些重复:

type ActionMapping = {
  ADD_TODO: AddTodoAction;
  REMOVE_TODO: RemoveTodoAction;
}
interface Action { type: keyof ActionMapping }; // useful for consistency
interface AddTodoAction extends Action {
  type: 'ADD_TODO'; // manually cross-reference
  description: string;
}
interface RemoveTodoAction extends Action {
  type: 'REMOVE_TODO'; // manually cross-reference
  id: number;
}
// if you want more Action types you need to add it to ActionMapping:
interface BadAction extends Action {
  type: 'BAD'; // error, BadAction incorrectly extends Action
  title: string;
}

现在你可以定义你想要的:

type ActionReducers = {
  [P in keyof ActionMapping]: (state: State, action: ActionMapping[P]) => State
};

这是另一种方式,重复较少,但更复杂:

// define all your data types here without the type property
type ActionDataMapping = {
  ADD_TODO: { description: string },
  REMOVE_TODO: { id: number }
}

// the ActionMapping programmatically adds the right tag to the data  
type ActionMapping = {
  [K in keyof ActionDataMapping]: ActionDataMapping[K] & { type: K };
}

// and an Action is just the union of values of ActionMapping properties    
type Action = ActionMapping[keyof ActionMapping];

// this is the same as above
type ActionReducers = {
  [P in keyof ActionMapping]: (state: State, action: ActionMapping[P]) => State
};

一切都应该在这里工作。您的Action 子类型缺少好听的名称。如果您愿意,可以将它们添加回来,但这有点重复:

// if you need names for them:
type AddTodoAction = ActionMapping['ADD_TODO'];
type RemoveTodoAction = ActionMapping['REMOVE_TODO'];

希望其中一项对您有用。祝你好运!

【讨论】:

    猜你喜欢
    • 2021-02-14
    • 2020-05-07
    • 2015-04-26
    • 1970-01-01
    • 2020-02-16
    • 1970-01-01
    • 1970-01-01
    • 2015-08-30
    • 2020-04-14
    相关资源
    最近更新 更多