我想写一个如下所示的方法。接口A
是生成器的配置类型。我将对不同类型的生成器进行不同的配置,并在函数f
中同时使用它们,以便在元组中获得所有生成的值。
interface A<Output> {
f: () => Output;
};
const a: A<string> = {
f: () => 'Hello',
};
const b: A<number> = {
f: () => 42,
};
function f(...x: ???): ??? {
return x.map(x => x.f());
}
const y: [string, number] = f(a, b);
console.log(y) // ['Hello', 42]
我如何在没有任何错误的情况下实现这一点?
您可以将函数表示为作用于泛型映射元组的类型。如果输出是T
类型的元组,那么输入是{[I in keyof T]: A<T[I]>}
类型,这意味着:对于T
元组中的每个索引I
,输入应该是A<T[I]>
类型,输出是T[I]
:类型
function f<T extends any[]>(...x: { [I in keyof T]: A<T[I]> }) {
return x.map(x => x.f()) as T;
}
请注意,编译器没有也可能永远无法验证x.map(...)
是否实际生成了预期类型的值;即使通常表示这样的转换也需要更高级的kinded类型之类的东西,而TypeScript目前不支持这些类型(有关相关功能请求,请参阅microsoft/TypeScript#1213(。最简单的方法是我上面所做的:通过编写x.map(...) as T
,断言输出的类型为T
。
让我们确保它有效:
const y = f(a, b); // [string, number]
console.log(y[0].toUpperCase()) // "HELLO"
console.log(y[1].toFixed(2)) // "42.00"
是的,看起来不错。
哦,您可以将此转换表示为使用ReturnType<T>
实用程序类型的东西,但编写起来更麻烦:
function f<T extends A<any>[]>(...x: T) {
return x.map(x => x.f()) as {
[I in keyof T]: ReturnType<Extract<T[I], A<any>>["f"]>
};
}
不过,从调用方的角度来看,它的行为类似。
到代码的游乐场链接