mosya<TC> - Readonly型を定義してみようの解説
この記事はmosya<TC>の問題の一つであるMyReadonly型の解説になります。
問題
組み込みの型ユーティリティReadonly<T>
を使用せず、T
のすべてのプロパティを読み取り専用にする型を実装します。実装された型のプロパティは再割り当てできません。
例えば:
interface Todo {
title: string;
description: string;
}
const todo: MyReadonly<Todo> = {
title: "Hey",
description: "foobar",
};
todo.title = "Hello"; // Error: cannot reassign a readonly property
todo.description = "barFoo"; // Error: cannot reassign a readonly property
前提知識
この問題を解くにあたって型についての以下の知識を理解しておく必要があります。
- ジェネリクスを理解する
- keyofを理解する
- Mapped Typesを理解する
- インデックスアクセス型を理解する
- readonly修飾子を理解する
ジェネリクスを理解する
まず、<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
型を継承した型に制約されます。
keyofを理解する
keyof
は、オブジェクトのキーを取得するキーワードです。keyof T
とすると、T
のキーを取得することができます。
interface Todo {
title: string;
description: string;
}
type TodoKeys = keyof Todo; // "title" | "description"
ここで、|
というキーワードが出てきますが、これはユニオン型と呼ばれる型です。ユニオン型は、A | B
というように、A
またはB
の型を表します。
Mapped Typesを理解する
Mapped Types
はユニオン型を使って、新しいオブジェクトの型を生成する機能です。
例えば、以下のようなユニオン型があったとします。
type TodoKeys = "title" | "description";
このユニオン型を使って、以下のようなオブジェクトの型を生成することができます。
type Todo = {
[K in TodoKeys]: string;
};
// 以下のように展開される
type Todo = {
title: string;
description: string;
};
インデックスアクセス型を理解する
インデックスアクセス型
は、オブジェクトのプロパティにアクセスするための機能です。
例えば、以下のようなオブジェクトがあったとします。
interface Todo {
title: string;
description: string;
}
このオブジェクトのプロパティにアクセスするには、以下のようにします。
type Title = Todo["title"]; // string
このように、Todo["title"]
とすることで、Todo
のtitle
プロパティの型を取得することができます。
これをインデックスアクセス型と呼びます。
先ほど登場したMapped Types
と組み合わせることで、一つずつオブジェクトのプロパティにアクセスすることができます。
readonly修飾子を理解する
readonly
修飾子は、プロパティを読み取り専用にする修飾子です。
この修飾子があると、プロパティの再割り当てができなくなります。
interface Todo {
readonly title: string;
description: string;
}
const todo: Todo = {
title: "Hey",
description: "foobar",
};
todo.title = "Hello"; // Error: cannot reassign a readonly property
todo.description = "barFoo"; // OK
まとめ
以上の知識を使って、以下のように解答を書くことができます。
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
[K in keyof T]
を使うことで、T
のすべてのプロパティをユニオンとして取得してそれをオブジェクトのキーとして列挙します。- 列挙したプロパティを
readonly
にするには、readonly
修飾子を使います。 T[K]
のようにインデックスアクセス型を使って、T
のプロパティの型をそれぞれ取得しています。
Authored by
Godai@steelydylan
Webサービスを作るのが好きなWebエンジニア。子供が産まれたことをきっかけに独立し法人化。サービス開発が大好き。
好きな言語はTypeScript。