💎

【TypeScript】ユーティリティ型をConditional Typesで再定義して、inferの理解を深める

2024/10/17に公開

こんにちは、ujitaです!

前回の記事に引き続き、ユーティリティ型をConditional Typesとinferを使ってどのように定義するのか解説していきます。ユーティリティ型やConditional Typesについては前回の記事で説明したので、省略します。知らない方や忘れてしまった方はぜひ前回の記事を読んでいただけると幸いです。

https://zenn.dev/ujita/articles/2327748327996c

本記事では、TypeScriptに標準で用意されているユーティリティ型であるParametersInstanceTypeを、Conditional Typesとinferを用いてどのように定義するかを解説します。

ParametersとConditional Typesを使った定義

Parametersとは?

TypeScriptの組み込みユーティリティ型である Parameters<Type> は、関数型 Type の引数の型をタプルとして抽出します。例えば、以下のように使用します。

Parameters<Type>

function example(a: string, b: number): void {}

type ExampleParams = Parameters<typeof example>; // [a: string, b: number]

上記の例では、ExampleParams は[a: string, b: number]というラベル付きタプル型になります。

他には下記のような挙動をします。

type T0 = Parameters<() => string>;
// type T0 = [] 引数がないので、空のタプル型

type T1 = Parameters<<T>(arg: T) => T>;
// type T1 = [arg: unknown] 引数の型がジェネリクスの時は、unknown

type T2 = Parameters<any>;
// type T2 = unknown[] 引数の型がanyの時は、unknownのタプル型

Conditional Typesを使った定義

ParametersをConditional Typesを用いて再実装するためには、関数型から引数の型を抽出する必要があります。これを実現するために、inferを使用します。

infer(推論)とは?

inferは、Conditional Types内で型を推論(infer)するために使用されます。extendsの右辺にのみ書くことができます。具体的には、型の一部を推論して新しい型変数として取り出すことができます。

T extends SomeType<infer R> ? R : never

ここで、SomeTypeの中からRという型を推論(抽出)し、条件が真の場合はR型を返し、偽の場合はnever型を返します。

Conditional Typesとinferで再定義する

では、実際にParametersをConditional Typesとinferを用いて再定義してみましょう。基本的な構造は以下のようになります。

type MyParameters<T> = T extends (...args: infer P) => any ? P : never;

T extends (...args: infer P) => anyの解説

  • Tが任意の引数...argsを持つ関数型であるかをチェックしています。
  • infer Pを使って、関数の引数部分を推論し、型変数Pとして抽出します。

? P : neverの解説

  • 条件が真の場合(Tが関数型であれば)、抽出した引数型Pを返します。
  • 偽の場合はnever型を返します。

以下の使用例のように元のParametersと同様に機能します。

function example(a: string, b: number): void {}

type ExampleParams = MyParameters<typeof example>; // [a: string, b: number]

InstanceTypeとConditional Typesを使った定義

次に、InstanceTypeをConditional Typesとinferを用いて再定義してみましょう。

InstanceTypeとは?

TypeScriptの組み込みユーティリティ型である InstanceType<Type> は、コンストラクタ関数型Typeからそのインスタンスの型を抽出します。例えば、以下のように使用します。

class C {
  x = 0;
  y = 0;
}

type T0 = InstanceType<typeof C>; // C

Conditional Typesを使った定義

では実際に、InstanceType<Type>をConditional Typesとinferを用いて再定義してみましょう。

type MyInstanceType<T> = T extends new (...args: any[]) => infer R ? R : never;

T extends new (...args: any[]) => infer Rの解説

  • Tが任意の引数...argsを受け取るコンストラクタ関数型であるかをチェックしています。
  • infer Rを使って、コンストラクタが生成するインスタンス型Rを推論し、抽出します。

? R : neverの解説

  • 条件が真の場合、抽出したインスタンス型Rを返します。
  • 偽の場合はnever型を返します。

以下の使用例のように元のInstanceTypeと同様に機能します。

class C {
  x = 0;
  y = 0;
}

type T0 = MyInstanceType<typeof C>; //C

まとめ

今回の記事では、TypeScriptのユーティリティ型であるParametersとInstanceTypeを、Conditional Typesとinferを用いて再定義する方法を解説しました。inferを使用することで、型の一部を抽出し、より柔軟な型操作が可能になります。前回の記事と合わせて読んでいただけると、Conditional Typesとinfer、ユーティリティ型について理解が深まるのではないかと思います。今後も、TypeScriptの記事を書いていきたいと思うので、いいねとフォローをよろしくお願いいたします!

https://zenn.dev/articles/b88a5d513c2f27

Discussion