type-challengesメモ 〜easy編〜

2021-12-25

なにこれ

type-challengesの解答メモ

多少凝った型でもググりながら書いているのでなんとかなるでしょう。と思っていたのですが、全然わからん。。。
これを超えたらきっと理解度増してるだろうと信じて解いていきます。

easy

初級なんて簡単やろw

そう思っていたのですが洗礼を受けました。
気持ちを改めてせめて初級くらいは全部解きます。

  • Pick
  • Readonly
  • Tuple to Object
  • First of Array
  • Length of Tuple
  • Exclude
  • Awaited
  • If
  • Concat
  • Includes
  • Push
  • Unshift
  • Parameters

Pick

Pick

組み込みの型ユーティリティPick<T, K>を使用せず、TからKのプロパティを抽出する型を実装します。

  • やること
    • Tに含まれるキーのみをKで受け取れるようにする
    • Kを型のキーとする
    • TからKに対応する型を持ってくる
初期状態
type MyPick<T, K> = any

まずはTに含まれるキーのみをKで受け取れるようにします。

type MyPick<T, K extends keyof T> = any
  • keyof TでT型のキーを全て列挙します。
  • extendsでKの条件を指定します。
  • K extends keyof TでKはT型のキーのみを受け付けるようになります。

次にKをMyPick型のキーに指定します。

type MyPick<T, K extends keyof T> = {
  [N in K]: any
}
  • [N in K]でKの内容をforループのように展開します。Mapped Typesという機構です。
  • [N in K]: anyでKを展開してキーにして、その型はany型であると定義しています。

最後にTからKに対応する型を持ってきます。

type MyPick<T, K extends keyof T> = {
  [N in K]: T[N]
}
  • T[N]でT型のNに対応するキーの型を取得します。Lookup Typesという機構です。

これで完了です。

Readonly

Readonly

組み込みの型ユーティリティReadonly<T>を使用せず、T のすべてのプロパティを読み取り専用にする型を実装します。実装された型のプロパティは再割り当てできません。

  • やること
    • Tのキーと型をそのまま展開する
    • readonlyを修飾させる
初期状態
type MyReadonly<T> = any

まずはT型をそのまま展開します。

type MyReadonly<T> = {
  [K in keyof T]: T[K]
}
  • keyof, Mapped Types, Lookup Typesを利用してT型をそのまま展開します。

次にreadonlyを修飾させます。

type MyReadonly<T> = {
  readonly [K in keyof T]: T[K]
}
  • readonlyを指定するだけです。

これで完了です。

Tuple to Object

Tuple to Object

タプルを受け取り、その各値のkey/valueを持つオブジェクトの型に変換する型を実装します。

  • やること
    • タプルの値をキーにセットする
    • 型はキーと同じ文字列リテラルをセットする
    • (問題がそうなっているので)stringだけで構成されているarrayの場合だけ利用できるようにする
初期状態
type TupleToObject<T extends readonly any[]> = any

まずはタプルの値をキーにセットします。

type TupleToObject<T extends readonly any[]> = {
  [K in T[number]]: any
}
  • T[number]でTに入ってくるタプルのindexに対応した値をとってくる。
    • 詳しくはこれを読むのが良さそうです。

次に型はキーと同じ文字が入るようにします。

type TupleToObject<T extends readonly any[]> = {
  [K in T[number]]: K
}
  • 文字列リテラルで、キーと型は同じ文字列となります。

最後に問題としてnumber[]の場合はエラーにしろとテストに書いてあるのでそうします。

type TupleToObject<T extends readonly string[] = {
  [K in T[number]]: K
}

以上です。

First of Array

First of Array

配列Tを受け取り、その最初のプロパティの型を返すFirst<T>を実装します。

  • やること
    • Tの最初の値を取得する
    • 配列が空の場合はneverを返す
初期状態
type First<T extends any[]> = any

まずはTの最初の要素を取得します。

type First<T extends any[]> = T[0]
  • 単純にindexerを利用して配列の最初の値を取得します

その後、配列がからの場合はneverを返します

type First<T extends any[]> = T extends [] ? never : T[0]
  • T extends 条件 ? : でTの内容によって返却するものを変えるように三項演算子を使います。
    • Tが空配列の場合はneverを返す
    • Tが空配列ではない場合はT[0]を返す

以上です。

Length of Tuple

Length of Tuple

タプルTを受け取り、そのタプルの長さを返す型Length<T>を実装します。

  • やること
    • Tの長さを取得する
    • タプル以外は受け付けられないようにする
初期状態
type Length<T extends any> = any

まずは長さを取得します。

type Length<T extends any> = T['length']
  • タプルについてはドキュメントを参照するとlengthを保持しているとのことですので、これで長さを取得できます。

次にタプルのみを受け付けられるようにします。

type Length<T extends readonly any[]> = T['length']
  • T extends readonly any[]で、タプルのみを受け付けられるようにします。
    • 問題で、const t = ['a', 'b'] as constとタプルを作成しており、この場合の型を読むとreadonlyが修飾しています。
    • ドキュメントにこの挙動についての説明があります。

以上です。

Exclude

Exclude

組み込みの型ユーティリティExclude <T、U>を使用せず、Uに割り当て可能な型をTから除外する型を実装します。

  • やること
    • Tを全て型として展開する
    • Uで受け取った値は型のキーからは除外する
初期状態
type MyExclude<T, U> = any

これは今まで回答してきたことを使えばできるのでサクッと行いましょう。

type MyExclude<T, U> = T extends U ? never : T
  • T extends U でUを含む場合かどうかで条件分岐させます。
  • neverは戻り値がないことを明示できます。

以上です。

Awaited

Awaited

Promise ライクな型が内包する型をどのように取得すればよいでしょうか。 例えば、Promise<ExampleType>という型がある場合、どのようにして ExampleType を取得すればよいでしょうか。

  • やること
    • inferを使って、Promiseの中の型を取得して条件分岐する
    • Promise<Promise<string | number>>のようにPromiseが二重に囲まれている場合に対応する
初期状態
type MyAwaited = any

Promise<string>のstringを取得するにはどうすれば良いのか?
inferを以下のように利用するとstringを取得できます。

type MyAwaited<T> = T extends Promise<(infer R)> ? R : never

次に問題ではPromise<Promise<string | number>>とPromiseを二重で囲っているテストケースが存在するので、それに対応します。

type MyAwaited<T> = T extends Promise<infer R>
  ? R extends Promise<infer U> ? U : R
  : never
  • extendsと三項演算子で二重のPromiseにまで対応しました。

最後に、MyAwaitedにPromiseが入ってこない場合にエラーにさせます。

type MyAwaited<T extends Promise<any>> = T extends Promise<infer R>
  ? R extends Promise<infer U> ? U : R
  : unknown
  • ジェネリクスでT extends Promise<any>の条件を追加してPromiseの何かが入ってこなければエラーにさせます。

以上です。

If

If

条件値CCが truthy である場合の戻り値の型TCが falsy である場合の戻り値の型Fを受け取るIfを実装します。 条件値Ctruefalseのどちらかであることが期待されますが、TF は任意の型をとることができます。

  • やること
    • Cがbooleanだけを受け付けるようにする
    • CがtrueだったらTを返して、それ以外はFを返すようにする
初期状態
type If<C, T, F> = any

Cはbooleanだけを受け付けられるようにします。

type If<C extends boolean, T, F> = any

次にCを判定してTかFを返却するようにします。

type If<C extends boolean, T, F> = C extends true ? T : F

以上です。

Concat

Concat

JavaScript のArray.concat関数を型システムに実装します。この型は 2 つの引数を受け取り、受け取ったイテレータの要素を順に含む新しい配列を返します。

  • やること
    • TとUは配列のみを受け取れるようにする
    • TとUを全て展開した配列を作成する
初期状態
type Concat<T, U> = any

T, Uを配列のみ受け取れるようにします。

type Concat<T extends unknown[], U extends unknown[]> = any
  • any[]でもunknown[]でも良いのかなと思います。

次にT, Uの値を全て展開します。

type Concat<T extends unknown[], U extends unknown[]> = [...T, ...U]
  • スプレッド構文でT,Uをそれぞれ展開します。展開した配列を返却すればConcatと同等の値を返却できます。

以上です。

Includes

Includes

Implement the JavaScript Array.includes function in the type system. A type takes the two arguments. The output should be a boolean true or false.

(日本語化されてませんでした)

…全くわからん…

Push

Push

Implement the generic version of Array.push

  • やること
    • Tを全て展開した後にUを追加する
    • TはArrayのみを受け取れるようにする
初期状態
type Push<T, U> = any

スプレッド構文を使えば終わりですね。

type Push<T extends unknown[], U> = [...T, U]

以上です。

Unshift

Unshift

Implement the generic version of Array.unshift

  • やること
    • Uをセットした後にTを全て展開する
    • TはArrayのみを受け取れるようにする
初期状態
type Unshift<T, U> = any

スプレッド構文を使えば終わりですね。

type Unshift<T extends unknown[], U> = [U, ...T]

以上です。

Parameters

Parameters

Implement the built-in Parameters generic without using it.

  • やること
    • …argsで受け取ったany[]型を抽出して返却する
初期状態
type MyParameters<T extends (...args: any[]) => any> = any

型を抽出するためにinferを使います。

type MyParameters<T extends (...args: any[]) => any> = 
  T extends (...args: infer U) => any
    ? U
    : never

最後のneverは条件に入らないと思われるのですが、三項演算子で書くのでfalseの場合に何かを返すために記載しています。

以上です。

終わりに

とりあえず初級をやってみて、TypeScriptなにも分かってなかったんだと気付かされました。。。
しっかり取り組んだら、結構実力着くんじゃないかと思います。
これチームでやったら面白いんじゃないかなーと感じますね。

Includesだけissueを見ても全然わからなかったので、別途取り組もうと思います。