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}
前提知識
この問題を解くにあたって型についての以下の知識を理解しておく必要があります。
Conditional Types
を理解するkey of
を理解するMapped Types
を理解する- インデックスアクセス型を理解する
Conditional Typesを理解する
Conditional Typesは、条件によって型を変更することができる機能です。
例えば、以下のような型が考えられます。
type Foo<T> = T extends string
? string
: number;
この型は、T
がstring
型を継承している場合は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"]
とすることで、Todo
のtitle
プロパティの型を取得することができます。
これをインデックスアクセス型と呼びます。
先ほど登場した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 F
とkeyof S
を使って、F
とS
のオブジェクトのキーをユニオン型として取得します。
これによって二つのオブジェクトのキー一覧をユニオン型として取得することができます。
次に、そのキーを使って、F
とS
のキー一覧から新しいオブジェクトの型を生成します。
type Merge<F, S> = {
[K in keyof F | keyof S]: ...
}
次にこのキーを意味するK
がS
に含まれるキーなのか、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。