mosya

mosya<TC> - Merge型を作って二つのオブジェクト型をマージしようの解説

この記事はmosya<TC>の問題の一つであるMerge型の解説になります。

問題

2 つの型をマージして新しい型を作ります。2 つ目に指定した型のキーは 1 つ目の型のキーを上書きします。
例えば以下の条件を満たすような型を作りましょう。

type foo = {
  name: string;
  age: string;
};
type coo = {
  age: number;
  sex: string;
};

type Result = Merge<foo, coo>; // expected to be {name: string, age: number, sex: string}

前提知識

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

  1. Conditional Typesを理解する
  2. key ofを理解する
  3. Mapped Typesを理解する
  4. インデックスアクセス型を理解する

Conditional Typesを理解する

Conditional Typesは、条件によって型を変更することができる機能です。

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

type Foo<T> = T extends string
  ? string
  : number;

この型は、Tstring型を継承している場合はstring型を、そうでない場合はnumber型を返します。

このように、extendsを使って条件を指定することで、型を変更することができます。

Mapped Typesを理解する

Mapped Typesはユニオン型を使って、新しいオブジェクトの型を生成する機能です。

例えば、以下のようなユニオン型があったとします。

type TodoKeys = "title" | "description";

このユニオン型を使って、以下のようなオブジェクトの型を生成することができます。

type Todo = {
  [K in TodoKeys]: string;
};

// 以下のように展開される
type Todo = {
  title: string;
  description: string;
};

key ofを理解する

key ofは、オブジェクトのキーをユニオン型として取得する機能です。

例えば、以下のようなオブジェクトがあったとします。

type Todo = {
  title: string;
  description: string;
};

このオブジェクトのキーを取得するには、以下のようにします。

type TodoKeys = keyof Todo; // "title" | "description"

インデックスアクセス型を理解する

インデックスアクセス型は、オブジェクトのプロパティにアクセスするための機能です。

例えば、以下のようなオブジェクトがあったとします。

interface Todo {
  title: string;
  description: string;
}

このオブジェクトのプロパティにアクセスするには、以下のようにします。

type Title = Todo["title"]; // string

このように、Todo["title"]とすることで、Todotitleプロパティの型を取得することができます。
これをインデックスアクセス型と呼びます。

先ほど登場したMapped Typesと組み合わせることで、一つずつオブジェクトのプロパティにアクセスすることができます。

便利な組み込み型 Exclude を理解する

Excludeは、ユニオン型から特定の型を除外することができる便利な組み込み型です。

例えば、以下のようなユニオン型があったとします。

type Foo = string | number | boolean;

このユニオン型からstring型を除外するには、以下のようにします。

type Bar = Exclude<Foo, string>; // number | boolean

解答例

以上の知識を使って、以下のように解答することができます。

type Merge<F, S> = {
  [K in
    | keyof F
    | keyof S]: K extends keyof S
    ? S[K]
    : K extends keyof F
    ? F[K]
    : never;
};

まず、keyof Fkeyof Sを使って、FSのオブジェクトのキーをユニオン型として取得します。
これによって二つのオブジェクトのキー一覧をユニオン型として取得することができます。
次に、そのキーを使って、FSのキー一覧から新しいオブジェクトの型を生成します。

type Merge<F, S> = {
  [K in keyof F | keyof S]: ...
}

次にこのキーを意味するKSに含まれるキーなのか、Fに含まれるキーなのかを判定し、条件ごとに型を変更します。

type Merge<F, S> = {
  [K in
    | keyof F
    | keyof S]: K extends keyof S
    ? S[K]
    : K extends keyof F
    ? F[K]
    : never;
};

もし、Kがオブジェクト型のSに含まれるキーであれば、S[K]を返し、オブジェクト型のFに含まれるキーであれば、F[K]を返します。

Authored by

筆者の写真

Godai@steelydylan

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

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

詳しくはこちら
mosya

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

© 2023 - mosya. All rights reserved.