我有一些接口都扩展了基接口。例句:
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
是子接口的联合)为提供了一些键入提示,这有助于防止根本不存在的键,但仍然允许使用来自其他接口的键。
一个可能的解决方案是使用函数重载:
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
这可以通过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
您可以通过以下几个示例调用尝试此方法:
游乐场