type-challenges の 1 問目を丁寧に解く
はじめに
この記事は、GLOBIS Advent Calendar 2024 の 7 日目の記事です。
筆者はここ数年は主に TypeScript を使って開発をしているのですが、その型システムを深淵を覗くために、type-challenges を(途中まで)解いたことがあります。
主観ですが、Easy の中では一番最初の問題が一番難しかった記憶があります。n 番煎じにはなりますが、今日はこの最初の問題を丁寧に見ていこうと思います。
task
type-challenges/questions/00004-easy-pick/README.md at main · type-challenges/type-challenges
Implement the built-in Pick<T, K> generic without using it.
Constructs a type by picking the set of properties K from T
For example:
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = MyPick<Todo, "title" | "completed">;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
};
solution
まず最初に、期待されていることを確認します。期待値は、MyPick
という型を実装することです。MyPick
は TypeScript 組み込みのPick
[1]と同じ振る舞いをすればよいということなので、まずはPick
のドキュメントを確認するのがいいでしょう。
やるべきことはわかったので、一歩ずつこの型を完成させていきます。
type MyPick<T, K> = any;
まず、T
に注目してみます。T
は特に制約はなさそうです。
次に、K
に注目してみます。K
はT
のプロパティ名だけを受け付ける必要がありそうです。型引数に制約をつけるにはどうしたらいいでしょうか。
ここで登場するのが、extends
[2]です。
type MyPick<T, K extends ...> = any;
では何をextends
すればいいでしょうか。先程の繰り返しになりますが、T
のプロパティ名ですね。ではT
のプロパティを列挙したい場合はどうすればいいでしょうか。
次に登場するのが、keyof
[3]です。keyof
を使えば、T
のプロパティ名をユニオン型[4]で手に入れることができます。
type MyPick<T, K extends keyof T> = any;
ここまでで左辺は埋まったので、残りの右辺を考えていきます。
K
はユニオン型になっているので、これをキー制約として使えれば良さそうです。ではこれを実現するにはどうすればいいかというと、Mapped Types[5]を使うことができます。
type MyPick<T, K extends keyof T> = {
[P in K]: any;
};
ここまで来ればあと一息、といった感じです。K
の要素をP
として取り出しているので、これを使ってT
から取り出していきます。ここでは、インデックスアクセス型[6]を使うことでこれが実現できます。
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
これで完成です。
Easy の 1 問目ですが、extends
にkeyof
、ユニオン型、Mapped Types、インデックスアクセス型と、1 問目にしては多くの概念が登場しました。実際の業務において自前で複雑な型を実装する機会はさほど多くないかもしれませんが、書けたり読めたりして損はないと思うので、コツコツと頑張っていきたいものです。
Happy Coding! :)
採用情報
ご興味ある方はこちらから!
Discussion