T extends (...args: infer R) => unknown ? R : never ← ナニコレ
type-challengesをやってるときの、ナニコレ シリーズです。前回はこれでした。
今回もやっていきます。前回同様、「あーなるほどね」となった方や「またinfer
の話か、どんだけこすんねん」となった方はわざわざ読む必要がない記事っぽいです。
手っ取り早く何か知りたい人・答え合わせしたい人
私が解説せずとも、ドキュメントで似たような使い方をちゃんと説明してくれているので、そっちを読むのが正解です。
extends
この文を読んでくださってる方は興味を持ってくださった方ですね。ありがとうございます。順を追って説明していきます。
まずはextends
からです。これも公式ドキュメントを読むのが一番いいのですが、Conditional Type Constraints
というヤツです。
使い方はConditional Type Constraints
という名前通り、型を条件分岐して決めたい場合に使います。例えばこんな感じ。
type ConditionalString<T> = T extends string ? string : T
type IdString = ConditionalString<"123456"> // string
type IdNumber = ConditionalString<123456> // 123456
ここではT
がstring
型であればそれを返し、そうでなければ元のT
の型を返しています。
話を戻して…
つまりT extends (...args: infer R) => unknown ? R : never
というのは、「T
が(...args: infer R) => unknown
という関数型かどうか?」を判定しているわけです。
infer
こっちも公式ドキュメントを読むのが一番手っ取り早いです。infer
は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 R
をany
としてしまうと…
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で演習
というわけで、この問題をやっていきます。
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管理とかでありそうです。
-
むしろ
infer
はextends
の条件式でしか使えません ↩︎
Discussion