为什么在使用concat函数将数组进行reduce操作时,TypeScript会推断出'never'类型?

83

代码胜过语言,因此:

['a', 'b', 'c'].reduce((accumulator, value) => accumulator.concat(value), []);

这段代码非常愚蠢,它只是返回一个复制的数组...

TypeScript 抱怨 concat 函数的参数:TS2345:类型“string”的参数不能赋值给类型“ConcatArray”的参数。

7个回答

110

我认为这是因为[]的类型被推断为never[],这是一个必须为空的数组类型。您可以使用类型转换来解决这个问题:

['a', 'b', 'c'].reduce((accumulator, value) => accumulator.concat(value), [] as string[]);

通常这不会是什么大问题,因为 TypeScript 会根据你对它执行的操作来找出更好的类型来分配给一个空数组。然而,由于你的示例是“愚蠢的”,正如你所说的那样,TypeScript 无法做出任何推断,将类型保留为 never[]


5
为什么TypeScript会推断出“never”类型?因为对于[]的类型被推断为never[]。如果这是真的,那么为什么const array = []; 推断出的类型是 any[] - Patrick Roberts
1
通常情况下,TypeScript会根据空数组的使用方式推断出正确的类型。但是,在这个例子中,它无法推断出正确的类型,因为回调函数的参数和返回类型没有任何类型,并且它没有使用原始的['a', 'b', 'c']数组进行类型推断。像const array = [];这样做确实会产生一个any[]数组。然而,我认为这是一个明确的例外,以避免混淆人们。如果你做类似于[].filter(a => true);的事情,你会发现它推断出never[]类型而不是any[]类型。 - Matt H
2
相关:https://github.com/Microsoft/TypeScript/issues/18687比我预想的要复杂,但是当上下文类型化时,[] 被推断为某种“扩展 any[]”类型...... 显然在作为reduce()中的累加器传递时不会发生这种情况。 ‍♀️ - jcalz
1
@MattH 是的,TS 无法确定数组的类型,您可以将其强制转换或将类型作为模板参数传递以减少然后它就可以工作了! - millsp
刚刚发现了 https://github.com/microsoft/TypeScript/issues/29795,这可能是上述信息的权威来源。 - jcalz

42

一个更好的解决方案,它有两个变体避免了类型断言(又称类型转换):

  1. 使用string[]作为reduce方法的泛型类型参数(感谢@depoulo提到它):
['a', 'b', 'c'].reduce<string[]>((accumulator, value) => accumulator.concat(value), []);
  1. accumulator的值作为string[]类型输入(并避免在[]上进行类型转换):
['a', 'b', 'c'].reduce((accumulator: string[], value) => accumulator.concat(value), []);

在typescript playground中尝试这个解决方案

注意事项

  1. 如果可能的话,应该避免使用类型断言(有时称为类型转换),因为这会将一种类型转移到另一种类型。这可能会导致副作用,因为您手动控制将一个变量强制转换为另一种类型。

  2. 只有在设置strictNullChecks选项为true时才会出现此typescript错误。禁用该选项后,Typescript错误将消失,但这可能不是您想要的。

  3. 我引用了我在Typescript 3.9.2中获得的完整错误消息,以便Google可以找到此线程以供正在寻找答案的人使用(因为Typescript错误消息有时会随着版本而改变):

    No overload matches this call.
      Overload 1 of 2, '(...items: ConcatArray<never>[]): never[]', gave the following error.
     Argument of type 'string' is not assignable to parameter of type 'ConcatArray<never>'.
      Overload 2 of 2, '(...items: ConcatArray<never>[]): never[]', gave the following error.
     Argument of type 'string' is not assignable to parameter of type 'ConcatArray<never>'.(2769)
    

6
这应该是被接受的答案。使用泛型的略微不同的解决方案: ['a', 'b', 'c'].reduce<string[]>((accumulator, value) => accumulator.concat(value), []); - depoulo
1
感谢Andru的宝贵反馈。我也遇到了同样的问题,所以上网搜索并找到了您正确的答案。 - Ammamon

9
你应该使用泛型来解决这个问题。
['a', 'b', 'c'].reduce<string[]>((accumulator, value) => accumulator.concat(value), []);

这将设置初始空数组的类型,我认为这是最正确的解决方案。


3
您可以使用泛型类型来避免此错误。
请查看我展开函数的示例:
export const flatten = <T>(arr: T[]): T[] => arr.reduce((flat, toFlatten) =>
  (flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten)), [] as T[]);

3

设置类型的另外两种方式,我比强制类型转换(即[] as string)更喜欢的是:

  • <string[]>[]
  • Array<string>(0)

1

我在使用reduce函数时也遇到了同样的问题。之前的答案都可以,但如果你在将数组作为第二个参数传递给函数之前,先创建并输入数组,那么这也是非常简单明了的。

const alphabet = ['a', 'b', 'c']
let accumulatorArray: string[] = []

const reduction = alphabet.reduce((acc, curr) => {
    acc.concat(curr)
}, accumulatorArray)

-3

对我来说以上方法都不起作用,即使修改了tsconfig.json文件为"strict": false,我只能通过以下方式避免破坏应用程序:

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore

1
这并没有真正回答问题。如果您有不同的问题,可以通过点击提问来提出。如果您想在此问题获得新的答案时得到通知,您可以关注此问题。一旦您拥有足够的声望,您还可以添加悬赏以吸引更多关注。- 来自审核 - Andrew Halil

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接