LoginSignup
28
28

More than 1 year has passed since last update.

【TypeScript】型の問題集 type-challenges に挑戦!【解説つき】

Posted at

はじめに

この記事では、TypeScript の型の問題集 type-challenges についての簡単な説明を行い、例として数問の解き方と関連する TypeScript の機能を紹介します。

対象読者として、プリミティブ型、オブジェクト、ジェネリクス等、TypeScript の型に関する基礎知識が習得済みの方や、もっと TypeScript の型を使いこなせるようになりたい方を対象とします。

type-challenges とは

TypeScript の型に関する問題集です。以下のリポジトリに問題がまとまっています。

難易度は warm-up, easy, medium, hard, extreme となっています。難易度 warm-up は例題なので、実際は難易度 easy から取り組むことになると思いますが、難易度 easy でもそこそこ難しいです。

使い方の詳細は以下の記事が参考になります。

上記の記事で紹介された時よりも、問題数がかなり増えています!

image.png

それでは実際に解いてみましょう!

type-challenges に挑戦!

この記事では、例として、難易度 warm-up を1問と、難易度 easy を2問、問題とそれを解くために必要な TypeScript の知識・機能を解説します。

【難易度: warm-up】13・Hello World

問題は以下のページから確認できます。

この問題は、 HelloWorld という名前で、string と同じになる型を作る問題です。

// expected to be string
type HelloWorld = any

また、以下の test 型で、回答が正しいか確認されます。

// you should make this work
type test = Expect<Equal<HelloWorld, string>>

Take the Challenge ボタンから、Playground を立ち上げると、テスト用の型がエラーになっていることがわかります。

image.png

anystring に変えると、エラーがなくなります。

// expected to be string
type HelloWorld = string

このように、type-challenges は特定の条件を満たすような型を作成し、テスト用の型のエラーをなくせば正解です!

この問題は例題なので、さすがに簡単ですね。難易度 easy の問題を解いてみましょう!

【難易度: easy】7・Readonly

問題は以下のページから確認できます。

ユーティリティ型の Readonly を自作する、という問題です。
TypeScript には 様々なユーティリティ型が存在し、それを使うことで組み込みのジェネリクス型の変換がしやすくなります。

Readonly は、オブジェクト型の各プロパティを readonly にする、というユーティリティ型です。
例として、Readonly<{ x: number }>{ readonly x: number } になります。

この型を作るために必要な型の知識として、Mapped Types を紹介します。
Mapped Types は以下のような構文の型です。 K は型変数、T S には何か具体的な型を当てはめます。

{ [K in T]: S }

例として、T'x' | 'y'Snumber を当てはめてみます。

{ [K in 'x' | 'y']: number }

この型は、{ x: number, y: number } と同じ型になります。
これだけだとインデックス型に似ていますが、インデックス型とは異なり、プロパティのキーを制限できます。
また、それだけでなく、オブジェクトの各プロパティに対し、型の変換ができます。具体的には、readonly? の付与・除去など、他にもいろいろなことができます。したがって、今回の問題は Mapped Types を使って readonly を付与すればよさそうですね。

問題形式を見てみましょう。以下のようになっています。

type MyReadonly<T> = any

any を一旦 readonly を付与する Mapped Types に置き換えてみます。
以下のように、+readonly を追加することで、各プロパティは readonly が付いた型になります。

type MyReadonly<T> = { +readonly [K in XXX]: YYY }

あとは、型引数 T を使って適切な XXXYYY を作ります。
XXX にはプロパティのキーの型が必要です。 T のプロパティのキーの型は、 keyof を使って、keyof T とすることで得られます。YYY は各プロパティの値の型が必要です。型変数の K を使い、T[K] と書くことでえられます。(Lookup Types または Indexed Access Types とよばれます。)

以上により、Readonly を自作することができました!

type MyReadonly<T> = { +readonly [K in keyof T]: T[K] }

もう1問解いてみましょう!

【難易度: easy】4・Pick

問題は以下のページから確認できます。

組み込みのユーティリティ型 Pick を自作せよ、という問題です。以下のように、元の型に含まれるプロパティのうち指定したものだけ残すような型 MyPick を作ります。

interface Todo {
  title: string
  description: string
  completed: boolean
}

type TodoPreview = MyPick<Todo, 'title' | 'completed'>

const todo: TodoPreview = {
    title: 'Clean room',
    completed: false,
}

この問題も、Mapped Types を使用します。

type MyPick<T, K> = { [Key in XXX]: YYY }

XXX を第2型引数の K にすれば、プロパティが K のオブジェクトになりそうです。また、YYY は先ほどと同様に、T[Key] とすればよさそうです。

type MyPick<T, K> = { [Key in K]: T[Key] }

ですが、Playground で試してみると、エラーが残っています。

image.png

テストケースのエラーに注目します。3つ目のテストケースはエラーになることを想定していますが、エラーになっていないようです。このテストケースでは、 Todo のプロパティには存在しない invalid があり、Pick はこの場合にエラーになります。

これを防ぐためには、extends を使用した 型引数の制約 を行う必要があります。Kkeyof T の 部分型1 になっていればよいので K extends keyof T と書きます。型引数の制約をすると、以下のようになります。

type MyPick<T, K extends keyof T> = { [Key in K]: T[Key] }

これですべてのエラーがなくなりました!!

おわりに

たった2問解いただけで、ユーティリティ型・Mapped Types・keyof・Lookup Types・型引数の制約など、様々な TypeScriptの知識が得られます。このように、問題形式で楽しみながら TypeScript の型の勉強ができるので、type-challenges はおすすめです!

僕はまだ数問しか解いていませんが、今後もっと問題を解いていき、余裕があったら解説も記事にしていこうと思います。

おまけ

本記事で出てきた TypeScript の型に関する知識はそれぞれ以下でさらに深めることができます。

  1. 部分型に関して、以前に関連する記事を書いているので、ぜひ読んでみてください!
    TypeScript の型のエラーはどんなときに発生するのか?~部分型と変性で学ぶ TypeScript の型システム~

28
28
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
28
28