概要
TypeScript をまともに触ったことが無いので、使いながら覚えようということで type-challenges の 初級 を全問解答してみました。(2022/09/21現在)
と、格好をつけていますが、13問しかないのですぐです。
中級も8割ぐらい挑戦してみて、ある程度こういうケースはこういうふうに対応するんだという、ヒントとなるコツが見えてきたので、それもまとめてみます。
解答のヒント
配列を分割する
.ts
// T が any[] の場合
T extends [...infer A, infer B] ? A : never
// 上記の例では、T = [1,2,3,4] であれば、 A には [1,2,3], B には 4 が入る
// T が [] の場合、 never が返る
文字列を分解する
.ts
// T の最初の文字がLに入り、残りがRに入る
T extends `${infer L}${infer R}`
// T が xxxxhoge である場合、 X に xxxx が入る
T extends `${infer X}hoge`
配列をユニオン型にする
.ts
T[number]
// 使用例
type TupleToUnion<T extends unknown[]> = T[number]
TupleToUnion<[123, '456', true]> //=> 123 | '456' | true
ユニオン型を用いてオブジェクト型を作る(mapped types)
.ts
// K は文字列だと 1つのプロパティを持つオブジェクト型になるが、
// K がユニオン型だと、各型をプロパティとして持ったオブジェクト型になる
// 右辺(valueに該当する箇所) は P 以外でももちろん良い
// プロパティのキーは string | number | symbol 型である必要がある
// 下の例で言えば、 K extends string | number | symbol である必要がある
{ [P in K]: P }
// 特定のプロパティのみ残したい場合
// KからPを作り、そのPについて、Xを満たすかどうかで分岐をかける。
// never が返ってきた場合、生成されるオブジェクト型からそのプロパティは排除される
{ [P in K as P extends X ? P : never]: P }
配列の要素数を出す
数を条件に分岐をさせる場合にはこれをよく利用する。
A['length'] extends 5 ? true : false
←こういう使い方を再起処理で使ったりする。
.ts
type A = [1,2,3]
A['length'] // => 3
// ちなみに、string型に対して、同じ容量で文字数を出すことはできない
type S = "string"
S['length'] // => number
全く同じ型であるかどうかを確認する
.ts
Equal<A, B> extends true ? 真の場合 : 偽の場合
// A が false, B が boolean の場合に
// A extends B だと true になる
// このケースで false にしたい場合は Equal<A, B> を使う
以下解答
4 - Pick.ts
// K の型を縛る点がミソ
type MyPick<T, K extends keyof T> = {
[P in K]: T[P]
}
// 以下は間違い。
// K の型が定まっていないので、以下の点で明確ではなくなるので型エラーがでる
// 1. Kがプロパティキーに入れられる型かどうか、
// 2. Kから取り出したプロパティPがTのキーとして利用できる型かどうか
// type MyPick<T, K> = {
// [P in K]: T[P]
// }
7 - Readonly.ts
// readonly を各プロパティに付けているだけ
type MyReadonly<T> = {
readonly [P in keyof T]: T[P]
}
11 - Tuple to Object.ts
// 配列をユニオン型に変換するテクニック、 T[number] を使えば良い
// PropertyKey は、プロパティーキーに入れることができる型のユニオン型 string | number | symbol と同義
type TupleToObject<T extends readonly PropertyKey[]> = {
[P in T[number]]: P
}
14 - First of Array.ts
// 配列の最初 or 最後を取り出す時に infer を使うやり方 Array extends [infer F, ...infer B]
// T extends Tの条件 ? 合致する場合の型 : 合致しない場合の型
type First<T extends any[]> = T extends [infer A, ...infer B] ? A : never
// 以下は T が [undefined] の場合に never が返ってしまう
// type First<T extends any[]> = T[0] extends undefined ? never : T[0]
// 一番簡単なやり方
type First<T extends any[]> = T extends [] ? never : T[0]
18 - Length of Tuple.ts
// 配列から要素数を抜き出すやり方 T['length']
// 問題で渡される配列は as const がついているので、 型引数の中に readonly が必要
type Length<T extends readonly unknown[]> = T['length']
43 - Exclude.ts
// ユニオン型から特定の条件に合致する別のユニオン型を作る
// U と合致した型 T のみを使ったユニオン型を作っている
type MyExclude<T, U> = T extends U ? never : T
189 - Awaited.ts
// MyAwaited<T extends Promise<unknown>> で Promise型だけ引数にとるようにします
// T extends Promise<infer A> で 引数の型をAとして取り出します
// A extends Promise<unknown> で、Aが Promise型かどうかで分岐をかけています
type MyAwaited<T extends Promise<unknown>> = T extends Promise<infer A>
? A extends Promise<unknown>
? MyAwaited<A>
: A
: never
268 - If.ts
// extends を用いた三項演算子の基礎のような問題
type If<C extends boolean, T, F> = C extends true ? T : F
533 - Concat.ts
type Concat<T extends unknown[], U extends unknown[]> = [...T, ...U]
898 - Includes.ts
type Includes<T extends readonly any[], U> = T extends [infer A, ...infer B]
// ここが A extends U, もしくは U extends A だと
// [A, U]に [boolean, true] または [true, boolean] などの組み合わせがある場合に
// true 扱いになってしまうので、 Equal<>を使う
? Equal<A, U> extends true
? true
: Includes<B, U>
: false
3057 - Push.ts
type Push<T extends unknown[], U> = [...T, U]
3060 - Unshift.ts
type Unshift<T extends unknown[], U> = [U, ...T]
3312 - Parameters.ts
// (...args: any[]) => any と Function は 同義
// 関数型の引数の型を infer で A に移し、それをそのまま返している
// T は関数型なので、T extends (...args: infer A) => unknown が合致しないケースは無いので、
// 三項演算子の不一致時の戻り値は never
type MyParameters<T extends Function> = T extends (...args: infer A) => unknown
? A : never