我有以下字典,其中函数是多态的,接受相同的参数,但返回不同的结果:
const dict = {
one: {
foo<A>(a: A) {
return [a] as const
}
},
two: {
foo<A>(a: A) {
return [a, a] as const
}
},
three: {
foo<A>(a: A) {
return [a, a, a] as const
}
}
}
type Map = typeof map我希望从该字典中获得函数的返回类型,并使用以下助手:
const wrapper = <N extends keyof Map, A>() => {
const fn = (() => {}) as unknown as Map[N]['foo']
return null as unknown as ReturnType<typeof fn<A>>
}试图获得结果(例如,如果我将7作为第一个参数传递,那么名为7的函数将返回哪个函数)
// returns: "readonly [7] | readonly [7, 7] | readonly [7, 7, 7]"
// expected: "readonly [7, 7]"
type Result = typeof wrapper<'two', 7>问题是,我得到的结果是包含来自所有函数的返回类型的联合,而不是函数two的结果。
看起来,即使我只传递一个值作为参数,wrapper函数的泛型wrapper参数也被认为是一个联合。
我怎样才能得到正确的结果?复杂的是,我无法访问dict对象,因为它来自库。
简单性的TS游乐场代码
发布于 2022-08-11 21:19:59
正如我在注释中提到的,TypeScript目前无法表达您想要的类型转换,更不用说执行它了。按照微软/打字稿#1213中的要求,我们至少需要“泛型”或“高阶泛型”或“类型构造器参数”或“高类型类型”。TypeScript允许您编写特定类型的函数,但我们不能编写泛型函数。
也就是说,假设有一些神奇的类似类型的东西叫做TypeFunc,对于每一个F extends TypeFunc,F本身就是一个接受类型参数的泛型类型,所以我可以在没有编译器错误的情况下编写F<T>。也许我们可以写
// Don't do this, it's not valid TypeScript:
type Foo<F extends TypeFunc> = {x: F<string>}
type G = Foo<Array> // type G = {x: Array<string>}
type H = Foo<Promise> // type H = {x: Promise<string>}您认为Array和Promise是特定的类型函数,但是F是一个通用函数,它允许我们推迟将类型参数应用到Array和Promise。
然后,我们也许可以像这样定义您的wrapper()函数:
// Don't try this, it's not valid TypeScript:
type Input<T extends Record<keyof T, TypeFunc>> =
{ [K in keyof T]: { foo: <A>(...args: any) => T[K]<A> }};
type Output<T extends Record<keyof T, TypeFunc>, K extends keyof T, A> =
T[K]<A>;
declare const f: <T extends Record<keyof T, TypeFunc>>(
t: Input<T>) => <K extends keyof T, A>() => Output<T, K, A>;
const wrapper = f(dict);因为这样我们就可以将dict的类型表示为Input<T>,对于属性都是TypeFunc‘s的类型的T,wrapper()的输出将是Ouptut<T, K, A>类型,这就是当您使用T[K] ( TypeFunc )并将其作为类型参数传递给A时所得到的结果。
但是我们在TypeScript中没有这些更高的类型,所以我们不能这样做。
您使用实例化表达式的方法很有趣,但正如您注意到的那样,并没有做到这一点。在wrapper()的实现中,fn的类型是一个通用的索引访问类型,但是一旦调用它,就会得到所有可能的输出类型的友联市。这是一个TypeScript限制,如微软/打字稿#47240中所描述的。显然,编写fn<A>只允许您访问调用fn时会发生的事情,因此,通过使用类型参数实例化它,就会失去索引访问权限。
这是一个遗憾,因为实例化表达式看起来确实应该给我们一些能力来表达更高的类型。但他们没有。
那么,我们有什么选择?
我能得到的最接近的方法是对dict的每个键手动使用实例化表达式来构建一个特定的类型函数,该函数的行为是您想要的,然后让编译器验证dict实际上是否符合这个类型函数,以便在dict偏离这个类型函数时至少会收到警告。观察:
type DictMap<A> = {
one: ReturnType<typeof dict.one.foo<A>>,
two: ReturnType<typeof dict.two.foo<A>>,
three: ReturnType<typeof dict.three.foo<A>>,
};
const wrapper = <K extends keyof Dict, A>() => {
const dictSentinel: { [P in keyof Dict]: { foo(a: A): DictMap<A>[P] } } = dict;
// sentinel should let you know if something wrong has happened
return null! as DictMap<A>[K];
}类型DictMap<A>[K]类型准确地表示当k类型为K时调用K时所得到的信息,因此您只需从wrapper()返回DictMap<A>[K]
const x = wrapper<'two', 7>();
// const x: readonly [7, 7]如果有人更改了dict (添加了一个新属性或其他东西),那么您将在wrapper中的某个地方出现错误,无论是在DictMap<A>[P]类型中还是在dictSentinel赋值中。
不,不漂亮。除非您对dict类型的某些模式有更深入的了解,否则我想不出更好的方法。而理想的解决方案可能永远无法实现,除非我们在TypeScript中得到了一些更高的类型。
https://stackoverflow.com/questions/73320774
复制相似问题