mosya
mosya Business はこちら

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

前提知識

この問題を解くにあたって型についての以下の知識を理解しておく必要があります。

  1. ジェネリクスを理解する
  2. keyofを理解する
  3. Mapped Typesを理解する
  4. インデックスアクセス型を理解する
  5. readonly修飾子を理解する

ジェネリクスを理解する

まず、<T>というのは、ジェネリクスと呼ばれる機能です。ジェネリクスは、型をパラメータとして受け取ることができる機能です。

この受け取った値を使って、新しい型を生成することができます。
受け取った型を活用して、新しい型を生成することができるので、型の再利用性が高くなります。

例えば、以下のような型が考えられます

type Foo<T> = {
  bar: T;
};

この型は、Tという型を受け取り、barというプロパティにTを代入する型です。

この型を使うと、以下のように型を指定することができます。

type FooString = Foo<string>; // { bar: string }
type FooNumber = Foo<number>; // { bar: number }

このようにジェネリクスは型を引数として受け取って、新しい型を生成することができます。

型の制約

Textendsをつけることで制約をつけることができます。
例えば、T extends stringとすると、Tstring型か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"]とすることで、Todotitleプロパティの型を取得することができます。
これをインデックスアクセス型と呼びます。

先ほど登場した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。

ReactやTypeScriptなどの周辺技術が学べる
オンライン学習サービスを作りました!

詳しくはこちら
mosya

mosyaはオンラインでHTML,CSS,JavaScriptを基本から学習できるサービスです。現役エンジニアが作成した豊富なカリキュラムに沿って学習を進めましょう。

© 2023 - mosya. All rights reserved.