ウェブサイト検索

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

以下のいくつかの例の呼び出しで、この方法を試すことができます。

遊び場

関連記事