⚡️

T extends (...args: infer R) => unknown ? R : never ← ナニコレ

2024/11/10に公開

type-challengesをやってるときの、ナニコレ シリーズです。前回はこれでした。

https://zenn.dev/yskn_sid25/articles/87c5cde1be86b6

今回もやっていきます。前回同様、「あーなるほどね」となった方や「またinferの話か、どんだけこすんねん」となった方はわざわざ読む必要がない記事っぽいです。

手っ取り早く何か知りたい人・答え合わせしたい人

私が解説せずとも、ドキュメントで似たような使い方をちゃんと説明してくれているので、そっちを読むのが正解です。

https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#conditional-type-constraints

extends

この文を読んでくださってる方は興味を持ってくださった方ですね。ありがとうございます。順を追って説明していきます。

まずはextendsからです。これも公式ドキュメントを読むのが一番いいのですが、Conditional Type Constraintsというヤツです。

https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#conditional-type-constraints

使い方はConditional Type Constraintsという名前通り、型を条件分岐して決めたい場合に使います。例えばこんな感じ。

type ConditionalString<T> = T extends string ? string : T
type IdString = ConditionalString<"123456"> // string
type IdNumber = ConditionalString<123456> // 123456

ここではTstring型であればそれを返し、そうでなければ元のTの型を返しています。

話を戻して…

つまりT extends (...args: infer R) => unknown ? R : neverというのは、「T(...args: infer R) => unknownという関数型かどうか?」を判定しているわけです。

infer

こっちも公式ドキュメントを読むのが一番手っ取り早いです。inferInferring Within Conditional Typesというヤツです。

https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#inferring-within-conditional-types

使い方は、こっちもInferring Within Conditional Typesという名前通り、型の条件分岐内部で型を推論してほしい場合[1]に使います。

例えばこんな感じ。

こっちの例は公式ドキュメントの例をほぼそのまま拝借しちゃいます。

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

const func1 = () => "HelloWorld"
const func2 = (a: number, b:number) => a*b
type TypeFunc1 = GetReturnType<typeof func1> // string
type TypeFunc2 = GetReturnType<typeof func2> // number
type TypeFunc3 = GetReturnType<"123456"> // never

さっき覚えた通り、extendsは条件分岐です。今回の条件は何かというと、Tが関数型であればその戻り値の型を返し、そうでなければneverを返しています。

ここで登場しているのがinferで文字通りこれは推論です。この例の場合はサンプルコードから分かる通り、戻り値の型を推論しています。

例えばここでinfer Ranyとしてしまうと…

type GetReturnType<T> = T extends (...args: any[]) => any ? any : never;

const func1 = () => "HelloWorld"
const func2 = (a: number, b:number) => a*b
type TypeFunc1 = GetReturnType<typeof func1> // any
type TypeFunc2 = GetReturnType<typeof func2> // any
type TypeFunc3 = GetReturnType<"123456"> // never

となってしまい、「型の意味…」となってしまうわけです。

infer Rとしておくことで戻り値として実際に設定されている型はこれっぽいよヨと定義することができ、実際に渡された関数の戻り値を取得できるようにするというわけです。

type-challengesで演習

というわけで、この問題をやっていきます。

https://github.com/type-challenges/type-challenges/blob/main/questions/03312-easy-parameters/README.md

Implement the built-in Parameters generic without using it.

const foo = (arg1: string, arg2: number): void => {}

type FunctionParamsType = MyParameters<typeof foo> // [arg1: string, arg2: number]

さっきの例は戻り値でしたが、今度は「関数に設定された引数の型を返してね」ということらしいです。

extends, inferの使い方をマスターした皆さんであれば余裕ですね?答えはこんな感じのはずです。

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

const func1 = () => "HelloWorld"
const func2 = (a: number, b:number) => a*b
type TypeFunc1 = MyParameters<typeof func1> // []
type TypeFunc2 = MyParameters<typeof func2> // [a: number, b: number]
type TypeFunc3 = MyParameters<"123456"> // never

おわりに

これをちゃんと理解するにあたって色々見てたんですけど、公式のドキュメントが結局一番わかりやすかったです。

実際の使い道としてはReact Queryのkey管理とかでありそうです。

脚注
  1. むしろinferextendsの条件式でしか使えません ↩︎

Discussion