网站搜索

TypeScript 中的一组通用对象


type Foo<T = unknown> = {
  bar: T
  bar2: (v: T) => T
}

const f1 : Foo<number> = {
  bar: 42,
  bar2: (v: number) => 5 + v
}

const f2 : Foo<string> = {
  bar: "hello",
  bar2: (v: string) => v
}

// a function that takes an array of any Foo
function process: (Foo|string)[]) {
 ...
}


process([f1, f2]) // ERROR

它显示此错误:

Type 'Foo<number>' is not assignable to type 'string | Foo<unknown>'.
  Type 'Foo<number>' is not assignable to type 'Foo<unknown>'.
    Types of property 'bar2' are incompatible.
      Type '(v: number) => number' is not assignable to type '(v: unknown) => unknown'.
        Types of parameters 'v' and 'v' are incompatible.
          Type 'unknown' is not assignable to type 'number'

TypeScript 游乐场链接

我不清楚如何实现我的目标:拥有一个函数,它可以接受任何 T 的任何形式的 Foo 数组。

答案#1

一种专门针对 数组 类型为 [Foo<T0>, Foo<T1>, ..., Foo<TN>] 的输入获取此行为的方法是使您的函数在每个元素的类型参数的元组类型 T 上是泛型的。也就是说,T 将是 [T0, T1, ..., TN]。然后您可以将函数的输入定义为映射元组类型:

function extractValues<T extends any[]>(
  ar: [...{ [I in keyof T]: Foo<T[I]> | string }]
) {
}

这里的 ar 参数具有可变元组类型,它将 T 元组的每个元素映射到相应的 Foo 类型(或者我猜是 string,就像你例子中那样)。

让我们看看它的实际效果:

extractValues([f1, f2, "okay"]) // okay
// function extractValues<[number, string, unknown]>

这可以;编译器推断出 T 是元组类型 [number, string, unknown],因此 ar 应该是类型 [Foo<number> | string, Foo<string> | string, Foo<unknown> | string],实际上确实如此。注意 number 和 string 是从映射类型中推断出来的。第三个元组元素无法很好地推断,因此回退到 unknown,但这没关系,因为它是 string。

如果我们传入一个错误的输入会怎样?

extractValues([{ bar: 1, bar2: (v: string) => v.toUpperCase() }, f1, f2]); // error
// --------------------> ~~~~
// function extractValues<[string | number, number, string]>

这里 T 被推断为 [string | number, number, string]。虽然 f1 和 f2 没问题,但作为第一个元素传入的值有问题,因为 bar2 的类型不是 (v: number | string) => number | string

所以这也行;它表明编译器确实以一种 Array<Foo<any>> 无法做到的方式限制了输入。

代码的 Playground 链接

答案#2

既然你希望函数参数保持通用,你就需要让函数本身也通用(更多信息点击这里)

所以它大概会是这样的

// a function that takes an array of any Foo
function extractValues<T>(ar: Foo<T>[]) {
    // ...
}

extractValues([f1]) // this will now work

如果你希望数组接受混合类型的 Foo,你可以这样定义它的类型

function extractValues(ar: (Foo<number> | Foo<string>)[]) {
    // ...
}

extractValues([f1, f2]) // you can now pass a heterogeneous array

如果你非得这样做,这将接受任何类型的 foo。但使用 any 违背了 TypeScript 的初衷,所以被认为是一种代码异味

function extractValues(ar: Foo<any>[]) {
    // ...
}

extractValues([f1, f2]) // you can now pass a heterogeneous array