mosya<TC> - チェイナブルなオブジェクトに型をつけようの解説
この記事はmosya<TC>の問題の一つであるChainable型の解説になります。
問題
JavaScript では、チェイン可能なオプションがよく使われます。しかし、TypeScript に切り替えたとき、正しく型を付けることができますか?
この課題では、オブジェクトでもクラスでも何でもいいので、 option(key, value)
と get()
の 2 つの関数を提供する型を定義してください。option
では、与えられたキーと値を使って現在の config の型を拡張できます。最終的な結果は get
で取得することにしましょう。
例えば
declare const config: Chainable;
const result = config
.option("foo", 123)
.option("name", "type-challenges")
.option("bar", {
value: "Hello World",
})
.get();
// expect the type of result to be:
interface Result {
foo: number;
name: string;
bar: {
value: string;
};
}
この問題を解くために js/ts のロジックを書く必要はありません。型レベルのロジックだけを書いてください。
key
は string
のみを受け付け、value
は任意の型を受け付けると仮定しても構いません。同じ key
が 2 回渡されることはありません。
解答例
type Chainable<T = {}> = {
option: <K extends string, V>(
key: K extends keyof T ? never : K,
value: V
) => Chainable<
Omit<T, K> & Record<K, V>
>;
get: () => T;
};
Chainable
型は、option
とget
の2つのプロパティを持つオブジェクト型です。
まずはそれを前提に、option
とget
の型を定義していきます。
T
には初期値として空のオブジェクト型を指定します。
type Chainable<T = {}> = {
option: ...
get: () => ...
}
option
は、key
とvalue
を引数に取り、Chainable
型を返す関数です。
key
はstring
型のみを受け付けるので、K extends string
という条件を指定します。
value
は任意の型を受け付けるので、V
という型引数を指定します。
type Chainable<T = {}> = {
option: <K extends string, V>(key: K, value: V) => ...
get: () => ...
}
ここで、option
メソッドで同じキーに対して値が2回渡されることを防ぐために、以下のようにkey
に対して制限します。
type Chainable<T = {}> = {
option: <K extends string, V>(key: K extends keyof T ? never : K, value: V) => ...
get: () => ...
}
このようにkey
がすでにT
のキーに含まれている場合は、never
を返すようにして、key
が重複しないように保証します。
次に、option
メソッドの返り値の型を定義します。
type Chainable<T = {}> = {
option: <K extends string, V>(key: K extends keyof T ? never : K, value: V) => Chainable<T & Record<K, V>>
get: () => ...
}
Record<K, V>
は、K
とV
を受け取り、K
をキー、V
を値とするオブジェクト型を返す型です。
オブジェクトT
に今回option
メソッドで渡されたkey
とvalue
を追加した新しい型を返すようにします。
最後に、get
メソッドの返り値の型を定義します。
type Chainable<T = {}> = {
option: <K extends string, V>(
key: K extends keyof T ? never : K,
value: V
) => Chainable<T & Record<K, V>>;
get: () => T;
};
get
メソッドは、T
を返せばいいので、get: () => T
という型を指定します。
Authored by
Godai@steelydylan
Webサービスを作るのが好きなWebエンジニア。子供が産まれたことをきっかけに独立し法人化。サービス開発が大好き。
好きな言語はTypeScript。