LoginSignup
2
1

More than 3 years have passed since last update.

TypeScriptのUnion Typeをプロパティの値によってフィルタする (Redux)

Posted at

意味のわからないタイトルになってしまいましたがReduxなどでは意外とやりたくなるパターンかと思います。

type FooAction = {
  type: 'Foo',
  payload: string
};
type BarAction = {
  type: 'Bar',
  payload: number
};
type Action = FooAction | BarAction;

みたいなのがあったときに

type SomeAction = FilterActionByType<Action, 'Foo'>; // FooAction

のようなことがしたい。

結論

以下のようにします。

type FilterUnionByProperty<
  Union,
  Property extends string | number | symbol,
  Condition
> = Union extends Record<Property, Condition> ? Union : never;

冒頭のFilterActionByType

type FilterActionByType<Action, Type> =
  FilterUnionByProperty<Action, 'type', Type>

のようにできます。

仕組み

最初は以下のようなコードを書きました。

type FilterActionByType<Action extends { type: any }, Type> =
  Action['type'] extends Type ? Action : never;

しかしながら、これではなぜか皆neverになってしまいます。

調べたところ、この方法ではAction['type']distributive conditional typesとして扱われないためだとのこと(参考)

distributive conditional typesの説明を見ると

Conditional types in which the checked type is a naked type parameter are called distributive conditional types.

となっています。Action['type']は裸(naked)ではない型引数のため、distributive conditional typesとならないのです。

先ほどのStack Overflowの回答を見ると、以下のようなコードも載っていました。

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

A extends any ? ~ : neverによりラップすることで問題を回避していますね。なんてトリッキーな...。

いやはや、TypeScriptは奥が深い。そして設計者に改めて敬意を示したくなりますね!

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1