LoginSignup
12
6

はじめに

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

問題

まず KT のプロパティのキーのみにしたくなるので 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

問題

Cboolean に制限して 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 Type が入っています。
Union Type があるということは条件分岐で 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 Type が入るみたいなので [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 近いので、適当なタイミングで記事にしたいと思います。

12
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
6