型の扱い方 ー extends と infer
経緯
型を扱うにあたり、よく出てくる extends と infer の使い方についてよくわかってなかったので、テストプログラムを書いてみた。
中身の話
書いてみると、なーんとなくは分かる感じがする。
実際使ってみると、ん?みたいなのは多いので、たくさん使ってみるのが一番早いかも
- 型に対して制約をつけるための extends
- 型に対してマッチングした結果の「部分」から型を抜き出すinfer
どちらも型を扱う上でとても強力ですし、ユーティリティ系型(ParametersやReturnype、Awaited、などなどいっぱいあるやつ)もこのあたりの合成なんだろうなぁと知見は広がるかなと思います。
コード
extends
型のマッチを行うやつ。
Genericsを使いたいんだけど、普通にTだけかくと、型のany(何でも入る)になってしまうので、そこに この型を充足出来るものだけだぞ!って制約をかけるサンプルです。
TypeTest型を作ってますが、そこのTに、「Tはnumberを満たしてるやつだけだぞ!」と。
<T extends number>
書き方としては
<[対象の型] extends [型]>
こんな感じですね。
そうすると、Tが推論されるときに、その形はnumberの要件満たしてないから駄目だぞ!って弾いたりするのに使うことが出来ます。
このあと infer を使うときにも出てきますので、その時の使い方はそっちで。
関数の型抜き出し
infer の説明に入る前に、こっちはたまに使ったこともあった関数の型抜き出しの話
もともと、こんな感じで関数の方はユーティリティがあるんですよね。
こいつらも、extendsとinferで説明がつくので、まずはサンプルがてら。
infer
実際にinferで、関数の型を抜き出そうとすると... こんな感じになります。
infer は、extends でマッチさせた型の部分を抜き出すための仕組みって感じですね。
type FuncParam2WithInfer1 =
FuncParameters extends
[number, (infer U), ...boolean[]]
? U
: never
突如、型の中に三項演算子でてきて、私は「はぁ?」となりましたが
書き方としては
[対象の型] extends
[マッチさせたい型]
? [マッチしてたらこっち]
: [マッチしなかったらこっち]
みたいにかけるので、マッチさせたい型のマッチさせたい場所に
infer [型の引数]
を書いておくと、その名前が後ろの三項演算子のマッチした場合の中でつかえるようになります。
コレを踏まえてもう一度
type FuncParam2WithInfer1 =
FuncParameters extends
[number, (infer U), ...boolean[]]
? U
: never
これを読み解くと
FuncParameters と [number, (infer U), ...boolean[]] をマッチさせて
その中の1番目(0番目、1番目...なので)の型を抜き出して
マッチしている場合は U つまり 1番目の型
マッチしていない場合は never
を返してね。
という書き方になっています。
関数の型抜きだしユーティリティを紐解くとしたら
きっとこんな感じだろうってのを書いてみると、関数の型に当て込んで型を抜いてくればいいってことになるので、きっとこんな実装なんだろうなぁと想像してます。(あってるかは保証しないですw)
extends と infer の配列適用の話
ここはおまけ程度ですが、配列だけに焦点を絞って見ると、こんな感じになります。
inferを入れる位置によって、抜き出す場所が変わったりするのもポイントですが、anyが型のワイルドカードにようになっているのが面白い部分ですね。
anyはどんな型でも入ります。なので、どの型でもanyは充足している(anyに代入可能)ですね。
<T extends any> → <T> って感じだと思ってます。
最後に
私が学習した内容を書いているだけなので、間違え等あるかとは思いますので、何かお気づきの際はご指摘いただけますと幸いです。
Discussion