如何在Typescript中按属性过滤对象类型的联合?

Mat*_*vic 4 filter typescript redux union-types

想象一下以下简化设置:

import { Action, AnyAction } from 'redux'; // interface Action<Type> { type: Type } and type AnyAction = Action<any>

export type FilterActionByType<
  A extends AnyAction,
  ActionType extends string
> = A['type'] extends ActionType ? A : never;

type ActionUnion = Action<'count/get'> | Action<'count/set'>;

type CountGetAction = FilterActionByType<ActionUnion, 'count/get'>;
// expected: Action<'count/get'>
// received: never

Run Code Online (Sandbox Code Playgroud)

有没有办法做到这一点?(打字稿 3.7 是一个选项)

jca*_*alz 7

您想要FilterActionByType<A, T>采用联合类型A并分别对每个成员采取行动,然后采用所有结果并将它们联合到一个新联合中……这意味着您希望在联合上FilterActionByType<A, T>进行分配(至少在 中A)。您可以使用分布式条件类型来执行此操作,方法是确保您的条件类型的形式为type FilterActionByType<A, T> = A extends ...。将裸A作为检查类型会触发您想要的分布。

但是:您的条件类型的形式为type FilterActionByType<A, T> = A["type"] extends ...,其中A由属性查找“覆盖”,因此不是分布式的。这意味着 theA["type"] extends ActionType需要整个 union 值A,即(在您的情况下)ActionUnion。并ActionUnion["type"] extends "count/get"变成("count/get" | "count/set") extends "count/get",这是错误的。(X extends (X | Y)总是正确的,但(X | Y) extends X一般情况下不正确。)所以你得到never.


将您拥有的内容更改为分布式条件类型的最简单方法就是将您的定义包装在A extends any ? ... : never

export type FilterActionByType<
  A extends AnyAction,
  ActionType extends string
> = A extends any ? A['type'] extends ActionType ? A : never : never;

type ActionUnion = Action<'count/get'> | Action<'count/set'>;

type CountGetAction = FilterActionByType<ActionUnion, 'count/get'>;
// type CountGetAction = Action<"count/get">
Run Code Online (Sandbox Code Playgroud)

或者,您可以将原始条件类型重构为分布式而不用包装它:

export type FilterActionByType<
  A extends AnyAction,
  ActionType extends string
> = A extends { type: ActionType } ? A : never;

type CountGetAction = FilterActionByType<ActionUnion, 'count/get'>;
// type CountGetAction = Action<"count/get">
Run Code Online (Sandbox Code Playgroud)

支票A extends {type: ActionType}是 的分布式版本A["type"] extends ActionType

无论哪种方式都适合您,但后者可能更干净。


好的,希望有帮助;祝你好运!

代码链接


Sim*_*ver 5

Extract<T, U>对此非常有用,并且比条件类型简单得多。对于以下示例,您可以将其视为“过滤器”:

// cat meows but doesn't bark
type Cat = { meow: true, bark: false };

// dog and coyote bark
type Coyote = { bark: true };
type Dog = { bark: true };

// all the animals
type Animal = Cat | Dog | Coyote;

// Filter / Extract Animal types that are assignable to { bark: true }
type Barkers = Extract<Animal, { bark: true }>;


// Result: 
// type Barkers == Dog | Coyote
Run Code Online (Sandbox Code Playgroud)

可能对于 OP 问题不太有用,但是也有一些很酷的功能可以用于过滤映射类型(来自 TS 4.1)。