1

Why is it that TypeScript doesn't narrow the type of arrays?

function test(input: (string | number)[]): string[] {
  // The type of .map(...) reports that it returns string[].
  input = input.map(x => x.toString())
  // Type error: Type '(string | number)[]' is not assignable to type 'string[]'.
  return input
}

The workaround is not depending on it narrowing the type by just immediately using or assigning to a fresh variable:

function test(input: (string | number)[]): string[] {
  return input.map(x => x.toString())
}

function test(input: (string | number)[]): string[] {
  const newInput = input.map(x => x.toString())
  return newInput
}

I did try casting, but in hindsight that obviously only works on use, e.g. return input as string[], and will not narrow the type, as .map(...) already returns the correctly narrowed type.

It feels counter intuitive to me having to do these workarounds. Why is it that TypeScript cannot narrow this array type and are there better workarounds available?

I did look into the official documentation and looked at similar questions on Stack Overflow, but unless I have overlooked something, I haven't seen this particular question answered with anything else than just to reassign.

It is what I am doing in my own code for now, but I just wish I knew why it is as it is and if I can do better.

> tsc --version                                                                                                                                                                                                                                                                              
Version 4.2.3
4

3 回答 3

2

无论好坏,基于分配的缩小或通常通过控制流分析进行缩小(如在microsoft/TypeScript#8010中实现的, 仅当所涉及的变量具有联合类型时才会发生。联合类型是指类型本身直接是联合的地方, like{a: string} | {a: number}Array<string> | Array<number>. 具有联合类型属性 like 的单个对象类型{a: string | number}本身不是联合;也不是interface使用联合类型类型参数指定的泛型 . 在microsoft/TypeScript#16976Array<string | number>中有一个长期建议支持非联合控制流量变窄,但没有迹象表明何时或是否会实施。所以input = input.map(x => x.toString())不会修改input.

TypeScript 中还有其他缩小类型保护,例如in运算符或instanceof运算符,您可以编写自己的用户定义类型保护断言函数来缩小其输入的类型。这些都对您没有太大帮助;到目前为止,最好的解决方法是不要重用相同的变量来表示两种不同的非联合类型,如您所知。

于 2021-08-25T19:55:50.680 回答
1

在这种情况下,类型缩小的概念在这里并不适用,除非我们实际使用更多代码。

function isStringArray(x: any[]): x is string[] {
  return x.every(i => typeof i === "string");
}

function test(input: (string | number)[]): string[] {
  // The type of .map(...) reports that it returns string[].
  input = input.map(x => x.toString())
  if (isStringArray(input)) {
    return input;   
  }
  return [];
}

这是有效的,因为我们在类型谓词中使用了typeof 类型保护,该类型谓词在.if

因此,如果数组的每个元素都是一个字符串,则返回“true”,这意味着输入数组是一个string[]. 在 an 中使用时if,“true”路径缩小了inputto的类型,string[]我们可以成功返回。

因此,虽然您认为直接返回或首先分配给新变量是一种“解决方法”,但您可以在此处看到,如果您坚持重用,那么在此处使用 TypeScript 缩小实际上input比使用固有类型的替代方案要多得多的代码返回的.map.

打字稿游乐场

于 2021-08-25T19:54:00.373 回答
0

因为input已显式键入 as(string | number)[]并且您无法更改其类型。

因此,当您这样做时,input = input.map(x => x.toString())您将 a 分配给string[]已经键入为(string | number)[](兼容)的变量,而不是更改其input自身的类型。

这实际上非常有用,因为类型不是“可变的”。你也不应该改变你的变量,并且不能改变现有变量的类型可以帮助你避免犯这样的错误。

于 2021-08-25T19:38:25.633 回答