TypeScript:是否可以定义一个接受不同类型参数的可变函数?



我想编写一个泛型函数,它可以接受不同类型的可变数量的参数,并根据这些参数返回一个元组。

下面是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中具有泛型(打算成为元组类型),然后将fnsrest参数表示为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链接到代码

最新更新