我有一个typescript函数register(values, f)
,其中值的类型为{ key1: string, key2: string }[]
,f是一个接受变量context
的函数。context
是一个泛型类,它提供了一个函数getValueForKeys,其输入是key1
和key2
,它们对应于通过values参数传入寄存器的{ key1: string, key2: string }
对象之一。如何使这个泛型类类型安全,使得从f的函数定义中的上下文访问的所有键都是传递到register()
的键?我希望从register()
的用法中推断上下文,这样编写要注册的函数的开发人员就不需要向泛型传递类型。
我试着做了如下的解决方案:
type Key = { key1: string; key2: string; value: any };
type Values = Key[];
type Key2FromKey1<T extends Values, K1 extends T[number]['key1']> = T[number]['key1'] extends K1 ? T[number]['key2'] : never;
class Context<T extends Values> {
constructor(private keys: T) {}
getValueForKey<K1 extends T[number]['key1'], K2 extends Key2FromKey1<T, K1>>(key1: K1, key2: K2): any {
const matchingKey = this.keys.find(k => k.key1 === key1 && k.key2 === key2);
return matchingKey ? matchingKey.value : undefined;
}
}
function register<T extends Values>(
values: T,
f: (context: Context<T>) => void
) {
const context = new Context(values);
f(context);
}
register([{ key1: 'foo', key2: 'bar', value: 'value1' }, { key1: 'biz', key2: 'baz', value: 'value2' }], (context) => {
// This should succeed
const value = context.getValueForKey('foo', 'bar');
// This should fail
const value2 = context.getValueForKey('biz', 'buzz'); // Error: Argument of type '"buzz"' is not assignable to parameter of type '"baz"'.
});
然而,这似乎对value和value2都失败了,对于'bar'和'buzz',Argument of type 'string' is not assignable to parameter of type 'never'
1条答案
按热度按时间lmvvr0a81#
主要的问题是您对
Key2FromKey1<T, K1>
的定义实际上并没有提取输入数组类型的正确元素。由于T[number]
是Key
-ish类型的联合,因此您可以使用Extract
实用程序类型来过滤key1
可分配给K1
的联合,然后index into"key2"
属性的成员:另一个问题是,默认情况下,编译器只会推断出字符串值属性的
string
;因此,值{key1: "foo"}
将被解释为{key1: string}
,而您关心的文字类型"foo"
将丢失。您需要给予编译器一个提示,即register()
的第一个参数需要更严格地推断其类型,就像调用者使用const
Assert一样。我们可以通过为T
使用const
类型参数来做到这一点:哦,这意味着数组文字将被推断为
readonly
元组类型。这很好,但是Values
是Key[]
,并且会拒绝readonly Key[]
。所以我们再做一个修改来允许它:好了,这些就是变化。让我们测试一下:
T
被推断为因此,根据需要,对
getValueForKey("foo", ⋯)
的调用将第二个参数约束为"bar"
,而对getValueForKey("biz", ⋯)
的调用将第二个参数约束为"baz"
。Playground链接到代码