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

TypeScript: Is it possible to define a variadic function that accepts different types of arguments?

我想编写一个通用函数,它接受可能具有不同类型的可变数量的参数和returns基于这些参数的元组。

下面是 JavaScript 中的示例:

function evaluate (...fns) {
  return fns.map(fn => fn())
}

evaluate(
  () => 10
) // [ 10 ]

evaluate(
  () => 10,
  () => 'f',
  () => null
) // [ 10, 'f', null ]

在 TypeScript 中,我需要以某种方式将传播参数元组转换为结果元组:

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() generic in its arraylike output type T (intended to be a tuple type), and then represent the fns rest parameter as a mapped type on T, noting that mapped array/tuple types are also array/tuple types:

function evaluate<T extends any[]>(
  ...fns: { [I in keyof T]: () => T[I] }
) {
  return fns.map(fn => fn()) as T;
}

请注意 type assertion as T is necessary because the compiler cannot see that fns.map(fn => fn()) will have the effect of converting an array/tuple of function types to the array/tuple of corresponding return types. See 了解更多信息。

因为 {[I in keyof T]: () => T[I]} 是一个 同态 映射类型,我们直接在 keyof T 上映射(参见 for more information), the compiler is able to infer T from it(链接页面已弃用,但仍然准确,没有新页面存在 ‍♂️)。

让我们看看实际效果:

const x = evaluate(() => 10);
// const x: [number]

const y = evaluate(
  () => 10,
  () => 'f',
  () => null
)
// const y: [number, string, null]

看起来不错。编译器发现 x[number] 类型,y[number, string, null] 类型。在您传入未知 order/length:

的剩余参数的情况下,它的行为也很合理
const fs = [() => "a", () => 3];
// const fs: ((() => string) | (() => number))[]

const z = evaluate(...fs);
// const z: (string | number)[]

这里fsArray<(()=>string) | (()=>number)>类型,所以z是类似的类型Array<string | number>

Playground link to code