mosya<TC> - オブジェクト型の一部を選択する型を作ろうの解説
この記事はmosya<TC>の問題の一つであるMyPick型の解説になります。
問題
例えば以下のようなコードを満たすようにMyPick型を実装しましょう。
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = MyPick<
Todo,
"title" | "completed"
>;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
};
前提知識
この問題を解くにあたって型についての以下の知識を理解しておく必要があります。
- ジェネリクスを理解する
- Conditional Typesを理解する
- never型を理解する
ジェネリクスを理解する
まず、<T>というのは、ジェネリクスと呼ばれる機能です。ジェネリクスは、型をパラメータとして受け取ることができる機能です。
この受け取った値を使って、新しい型を生成することができます。
受け取った型を活用して、新しい型を生成することができるので、型の再利用性が高くなります。
例えば、以下のような型が考えられます
type Foo<T> = {
bar: T;
};
この型は、Tという型を受け取り、barというプロパティにTを代入する型です。
この型を使うと、以下のように型を指定することができます。
type FooString = Foo<string>; // { bar: string }
type FooNumber = Foo<number>; // { bar: number }
このようにジェネリクスは型を引数として受け取って、新しい型を生成することができます。
型の制約
Tはextendsをつけることで制約をつけることができます。
例えば、T extends stringとすると、Tはstring型かstring型を継承した型に制約されます。
Conditional Typesを理解する
Conditional Typesは、条件によって型を変更することができる機能です。
例えば、以下のような型が考えられます。
type Foo<T> = T extends string
? string
: number;
この型は、Tがstring型を継承している場合はstring型、そうでない場合はnumber型になります。
type FooString = Foo<string>; // string
type FooNumber = Foo<boolean>; // number
never型を理解する
never型は、発生しない値の型です。
この型を使うと、存在しない型をないものとして扱うことができます。
例えば、以下のような型が考えられます。
type Exclude<T, U> = T extends U
? never
: T;
この型は、TがUを継承している場合はnever型、そうでない場合はT型になります。
以下のような使い方ができます。
type Foo = Exclude<
string | number | boolean,
boolean
>; // string | number
string | number | booleanはbooleanを継承しているので、式に当てはめるとnever型になります。
よって、最終的にFooはstring | numberになります。
また、Tからundefinedとnullを除外する型もnever型を使って実装できます。
type NonNullable<T> = T extends
| null
| undefined
? never
: T;
このように、存在しないことが保証される型をnever型を使って表現することができます。
まとめ
以上の前提知識を踏まえて、この問題を解いていきましょう。
まず、MyPick型を実装するにあたって、以下のような型を考えることができます。
type MyPick<T, K> = {
[P in K]: T[P];
};
ただし、この状態だと型エラーになります。というのもKがTのプロパティであることが保証されていないからです。
そこで、KがTのプロパティであることを保証するために、以下のようにextendsを使って制約をつけます。
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
これで、KがTのプロパティであることが保証されました。
さらに実装を改善する
さらに、KにはTのプロパティ以外のユニオンが含まれてもいいように実装してみましょう。
キーにはstringやnumber、symbolなどのプリミティブ型が指定でき、Tのプロパティ以外のユニオンが含まれている可能性があるものとします。
type MyPick<
T,
K extends
| keyof T
| string
| number
| symbol
> = {
[P in K]: T[P];
};
この場合、T[P]の部分でエラーになります。というのも、TにはPというプロパティが存在しない可能性があるからです。
そこで、T[P]の部分を以下のように書き換えます。
type MyPick<
T,
K extends
| keyof T
| string
| number
| symbol
> = {
[P in K]: P extends keyof T
? T[P]
: never;
};
これで、TにはPというプロパティが存在しない場合はnever型になるので、確実にTのプロパティである場合のみオブジェクトに含まれるようになりました。
Authored by

Godai@steelydylan
Webサービスを作るのが好きなWebエンジニア。子供が産まれたことをきっかけに独立し法人化。サービス開発が大好き。
好きな言語はTypeScript。
Related









