我想编写一个泛型函数,它可以接受不同类型的可变数量的参数,并根据这些参数返回一个元组。
下面是JavaScript中的一个例子:
function evaluate (...fns) {
return fns.map(fn => fn())
}
evaluate(
() => 10
) // [ 10 ]
evaluate(
() => 10,
() => 'f',
() => null
) // [ 10, 'f', null ]
在TypeScript中,我需要将spread参数元组转换为结果:
function evaluate<T1, T2 ... Tn> (
...fns: [() => T1, () => T2 ... () => Tn]
): [T1, T2 ... Tn] {
return fns.map(fn => fn()) as [T1, T2 ... Tn]
}
evaluate(
() => 10
) // [ 10 ]: [number]
evaluate(
() => 10,
() => 'f',
() => null
) // [ 10, 'f', null ]: [number, string, null]
我尝试了一种简单的方法,为所有合理长度的元组创建重载:
function evaluate<T1> (
fn1: () => T1
): [T1]
function evaluate<T1, T2> (
fn1: () => T1,
fn2: () => T2
): [T1, T2]
function evaluate<T1, T2, T3> (
fn1: () => T1,
fn2: () => T2,
fn3: () => T3
): [T1, T2, T3]
function evaluate<T1, T2, T3> (
...fns: Array<(() => T1) | (() => T2) | (() => T3)>
): [T1] | [T1, T2] | [T1, T2, T3] {
return fns.map(fn => fn()) as [T1] | [T1, T2] | [T1, T2, T3]
}
但是它看起来很可怕,不能很好地扩展,并且在更复杂的函数体中会导致问题。
有什么办法可以动态地做到这一点吗?谢谢!
实现这一点的最简单方法是使evaluate()
在其数组输出类型T
中具有泛型(打算成为元组类型),然后将fns
rest参数表示为T
的映射类型,注意映射的数组/元组类型也是数组/元组类型:
function evaluate<T extends any[]>(
...fns: { [I in keyof T]: () => T[I] }
) {
return fns.map(fn => fn()) as T;
}
注意类型断言as T
是必要的,因为编译器看不到fns.map(fn => fn())
会将函数类型的数组/元组转换为相应返回类型的数组/元组。有关更多信息,请参见不使用强制类型转换将元组类型值映射到不同的元组类型值。
因为{[I in keyof T]: () => T[I]}
是一个同态映射类型,我们直接映射到keyof T
(参见什么是"同态映射类型")。的意思吗?获取更多信息),编译器能够从中推断出T
(链接页面已弃用,但仍然准确,没有新页面存在🤷♂️)。
让我们看看它的作用:
const x = evaluate(() => 10);
// const x: [number]
const y = evaluate(
() => 10,
() => 'f',
() => null
)
// const y: [number, string, null]
看起来不错。编译器看到x
的类型是[number]
,y
的类型是[number, string, null]
。在传入未知顺序/长度的rest参数的情况下,它也能正常工作:
const fs = [() => "a", () => 3];
// const fs: ((() => string) | (() => number))[]
const z = evaluate(...fs);
// const z: (string | number)[]
这里fs
的类型是Array<(()=>string) | (()=>number)>
,因此z
的类型是类似的Array<string | number>
。
Playground链接到代码