はじめに
TypeScript の型について、雰囲気でしか理解していない節があるので Type Challenges をやってみることにしました。
とりあえず初級編を解いてみたので、自分の解法と解説を書いていきます。
Type Challenges について
TypeScript の型についての問題を集めたリポジトリです。
ありがたいことに日本語対応がされています。
詳しい説明は以下の記事を参照すると良いです。
お試し
初級編の前にお試しの問題があるので、まずはこれを解いて Type Challenges の解き方を理解します。
13・Hello World
HelloWorld
という型を string
型にするだけの問題です。
コードを書いて確認するために 挑戦する
と書かれた青いボタンを押します。
そうすると TS Playground が開き、問題文・解答用コード・テストコードが書かれている状態になっています。
テストは初期状態だと型チェックのエラーが出ているので、エラーが無くなるように解答を変更できれば、正解となります。
実際に下記のように解答を変更すると、エラーが無くなることを確認できると思います。
- type HelloWorld = any // expected to be a string
+ type HelloWorld = string // expected to be a string
初級編
お試しの Hello World を解くことができたので、ここからは初級編を解いていきます。
4・Pick
まず K
を T
のプロパティのキーのみにしたくなるので keyof T
を用いて K
の型を制限します。
type MyPick<T, K extends keyof T> = any
そして K
で与えられる各プロパティのキーに応じて、プロパティの値の型を T
と合うようにしたいので Mapped Types で K
を反復処理し Indexed Access Types で T
から対応するプロパティの値の型を取り出します。
type MyPick<T, K extends keyof T> = {
[key in K]: T[key]
}
7・Readonly
4・Pick と似たようなことを行い、プロパティの先頭に readonly
を付けます。
type MyReadonly<T> = {
readonly [key in keyof T]: T[key]
}
11・Tuple to Object
T
をプロパティのキーとして使える型に制限したいので string | number | symbol
を表す PropertyKey
型を使います。
type TupleToObject<T extends readonly PropertyKey[]> = any
そして Indexed Access Types は T[number]
とすることで、配列の要素の型を取得できるので、これを用いて今までと似た処理をします。
type TupleToObject<T extends readonly PropertyKey[]> = {
[K in T[number]]: K
}
14・First of Array
配列の最初の要素を取るだけなので T[0]
で良さそうです。
type First<T extends any[]> = T[0]
テストケースをよく見ると、配列が空のときは never
である必要があるようなので Conditional Types で条件分岐をします。
type First<T extends any[]> = T extends [] ? never : T[0]
18・Length of Tuple
Indexed Access Types で length
プロパティを参照します。
タプルは固定長なので length
に対応する型は具体的な要素数になっています。
type Length<T extends readonly any[]> = T['length']
43・Exclude
Distributive Conditional Types によって実現できます。
type MyExclude<T, U> = T extends U ? never : T
189・Awaited
infer
を用いることで Promise
の中の型を取ることができます。
type MyAwaited<T> = T extends Promise<infer U> ? U : T
テストケースには Promise
の中に Promsie
があるものもあるので、再帰で処理します。
type MyAwaited<T> = T extends Promise<infer U> ? MyAwaited<U> : T
テストケースをよく見てみると Promise
と同じようなオブジェクトの型もあるので PromiseLike
を使うことにします。
type MyAwaited<T> = T extends PromiseLike<infer U> ? MyAwaited<U> : T
268・If
C
を boolean
に制限して Conditional Types で条件分岐をします。
type If<C extends boolean, T, F> = C extends true ? T : F
533・Concat
Variadic Tuple Types によって実現できます。
type Concat<T extends readonly any[], U extends readonly any[]> = [...T, ...U]
898・Includes
再帰によって一つずつ条件に合う型が無いかを見ていけばよさそうです。
type Includes<T extends readonly any[], U> =
T extends [infer First, ...infer Rest]
? First extends U
? true
: Includes<Rest, U>
: false
テストケースをよく見ると配列に boolean
などの Union Types が入っています。
Union Types があるということは条件分岐で Distributive Conditional Types が働いてしまいます。
この働きを抑制するために extends
の両側の型を []
で囲みます。
type Includes<T extends readonly any[], U> =
T extends [infer First, ...infer Rest]
? [First] extends [U]
? true
: Includes<Rest, U>
: false
U
にも Union Types が入るみたいなので [U] extends [First]
も確認します。
type Includes<T extends readonly any[], U> =
T extends [infer First, ...infer Rest]
? [First] extends [U]
? [U] extends [First]
? true
: Includes<Rest, U>
: Includes<Rest, U>
: false
条件分岐を減らすために少し工夫をします。
type Includes<T extends readonly any[], U> =
T extends [infer First, ...infer Rest]
? [First, U] extends [U, First]
? true
: Includes<Rest, U>
: false
readonly
修飾子の有無も区別する必要があるみたいです。
それなりに調べてみましたが、明らかに初級編とは言えない解決策しかなさそうなので、テストケースで使われている Equal
を使って解きます。
import type { Equal } from '@type-challenges/utils'
type Includes<T extends readonly any[], U> =
T extends [infer First, ...infer Rest]
? Equal<First, U> extends true
? true
: Includes<Rest, U>
: false
テストケースが追加されて難しくなってしまったみたいです。
以下のものが実装されたら、初級編と言えそうな気がします。
3057・Push
Variadic Tuple Types によって実現できます。
type Push<T extends any[], U> = [...T, U]
3060・Unshift
3057・Push と殆ど同じです。
type Unshift<T extends any[], U> = [U, ...T]
3312・Parameters
問題を TS Playground で開くと T extends (...args: any[]) => any
と最初から書かれています。
今回求めているのはこの args
の型なので infer
を使います。
type MyParameters<T extends (...args: any[]) => any> =
T extends (...args: infer U) => any
? U
: never
おわりに
解くために調べていくうちに、理解の浅かった部分の補強がかなりできたと思います。
次は中級編ですが、問題数が 100 近いので、適当なタイミングで記事にしたいと思います。