TypeScriptでネストされたオブジェクトのキーに型ヒントを付ける方法は?
私はすべてのインターフェースが基本インターフェースを拡張するいくつかのインターフェースを持っています。例えば:
interface Base {
sharedKey : "a" | "b"
}
interface ChildOne extends Base {
sharedKey : "a",
keyOne : "child_one"
}
interface ChildTwo extends Base {
sharedKey : "b",
keyTwo : "child_two"
}
すべての拡張インターフェースは、定義を絞り込む共有キーを定義しています。私は、多くのIDEで見たことがある機能を実装しようとしています。それは、ネストされたオブジェクトのキーに型ヒントを付けるものです。次のような関数を考えてみてください:
function doStuff( sharedKey, privateKey ) { // Something here};
この関数は a または b のキーを受け取り、そのキーに基づいて、privateKey が keyOne または keyTwo であることが許可されているかどうかを検出します。
例えば、これは許可されるべきです:
doStuff( 'a', 'keyOne' ); // Allowed
しかし、これはそうあるべきではありません:
doStuff( 'b', 'keyOne' ); // Error: keyOne does not exist on ChildTwo
これは可能ですか?テンプレートリテラルを使ってみましたが、問題はテンプレートリテラルから返される型が文字列であり、それを型として使用できないことです。
Record<Unions['sharedKey'], Unions>(ここで Unions は子インターフェイスのユニオンです)を使用することで、いくつかの型ヒントが提供され、存在しないキーを使用することを防ぐのに役立ちますが、他のインターフェイスのキーを使用することは依然として可能です。
回答 #1
一つの可能な解決策は、関数のオーバーロードを使用することです。
interface ChildOne {
sharedKey : "a",
keyOne : "child_one"
}
interface ChildTwo {
sharedKey : "b",
keyTwo : "child_two"
}
function doStuff( sharedKey: ChildOne['sharedKey'], privateKey: keyof ChildOne ):number
function doStuff( sharedKey: ChildTwo['sharedKey'], privateKey: keyof ChildTwo ):number
function doStuff( sharedKey: string, privateKey: string) {
// Something here
return 0;
};
doStuff( 'a', 'keyOne' ); // Allowed
doStuff( 'b', 'keyOne' ); // Error: keyOne does not exist on ChildTwo
回答 #2
これは Extract を使って拡張可能な方法で行うことができます:
function doStuff<
SharedKey extends Union["sharedKey"],
PrivateKey extends Exclude<keyof Extract<Union, { sharedKey: SharedKey }>, "sharedKey">
>(sharedKey: SharedKey, privateKey: PrivateKey) { ... }
ここで、Union はすべての子要素のユニオンです。
使用されている共有キーを取得し、そのキーを使って、そのキーを持っているユニオンから子を抽出します。
正しい子を取得したら、その子のキー("sharedKey"を除く)を取得します(sharedKeyもタイプヒントに表示したい場合は別ですが?)。
本質的には、これは基本的に @PabloCarrilloAlvarez の回答をジェネリクスとして表現したのと同じ考えです。
doStuff("a", "keyOne") // good
doStuff("b", "keyOne") // error
doStuff("b", "keyTwo") // good
doStuff("a", "keyTwo") // error
以下のいくつかの例の呼び出しで、この方法を試すことができます。
遊び場
