Как отфильтровать объединение типа объекта по свойству в Typescript? - PullRequest
0 голосов
/ 28 октября 2019

Представьте себе следующую упрощенную настройку:

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

Есть ли способ сделать это? (машинопись 3,7 является опцией)

1 Ответ

2 голосов
/ 28 октября 2019

Вы хотите, чтобы FilterActionByType<A, T> взял тип объединения A и воздействовал на каждого члена в отдельности, а затем взял все результаты и объединил их в новый союз ... это означает, что вы хотите от FilterActionByType<A, T> до распределить по союзам (по крайней мере, в A). Для этого вы можете использовать дистрибутивные условные типы , убедившись, что ваш условный тип имеет форму type FilterActionByType<A, T> = A extends .... Наличие в качестве отмеченного типа A запускает нужный вам дистрибутив.

Но: ваш условный тип имеет форму type FilterActionByType<A, T> = A["type"] extends ..., в которой A «облечен» поиском свойств и поэтому не является дистрибутивным. Это означает, что A["type"] extends ActionType принимает целое значение объединения для 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">

Или вы можете изменить свой исходный условный тип на дистрибутивный, не оборачивая его:

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">

Проверка A extends {type: ActionType} является дистрибутивомверсия A["type"] extends ActionType.

Любой способ должен работать для вас, но последний, вероятно, чище.


Хорошо, надеюсь, это поможет;удачи!

Ссылка на код

...