为什么 TypeScript 不缩小数组类型?

Why doesn't TypeScript narrow array types?

为什么 TypeScript 没有缩小数组的类型?

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
}

解决方法不依赖于通过立即使用或分配给新变量来缩小类型:

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
}

我确实尝试过铸造,但事后看来,这显然只适用于使用,例如return input as string[],并且不会缩小类型,因为 .map(...) 已经 returns 正确缩小了类型。

我觉得必须执行这些变通办法是违反直觉的。 为什么 TypeScript 无法缩小此数组类型,是否有更好的解决方法?

我确实查看了官方文档并查看了 Stack Overflow 上的类似问题,但除非我忽略了什么,否则我没有看到这个特定问题除了重新分配之外没有其他任何答案。

这就是我现在在自己的代码中所做的,但我只是希望我知道为什么会这样,以及我是否可以做得更好。

> tsc --version                                                                                                                                                                                                                                                                              
Version 4.2.3

因为 input 已经被显式输入为 (string | number)[] 并且您不能更改它的类型。

因此,当您执行 input = input.map(x => x.toString()) 时,您将 string[] 分配给已键入为 (string | number)[](兼容)的变量,而不是更改 [=10= 的类型]本身。

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

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

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 [];
}

之所以可行,是因为我们使用了 if 内部使用的 typeof type guard inside a type predicate

所以如果数组的每个元素都是一个字符串,return "true" 这意味着输入数组是一个 string[]。当在 if 中使用时,“真实”路径将 input 的类型缩小为 string[],我们可以 return 成功。

因此,虽然您认为直接 returning 或首先分配给一个新变量是一种“解决方法”,但您可以在这里看到,如果您坚持重新使用 TypeScript 缩小,实际上会增加代码 -使用 input 而不是使用 returned .map.

的固有类型的替代方案

TypeScript Playground

无论好坏,narrowing based on assignment or generally narrowing via control flow analysis (as implemented in microsoft/TypeScript#8010 only happens when the variables involved have union types. And by union type I mean where the type is itself directly a union, like {a: string} | {a: number} or Array<string> | Array<number>. A single object type with union-typed properties like {a: string | number} is not itself a union; nor is a generic interface specified with a union-typed type parameter like Array<string | number>. There is a longstanding suggestion at microsoft/TypeScript#16976 支持非联合控制流缩小,但没有迹象表明何时或是否会实施。所以 input = input.map(x => x.toString()) 不会修改 input.

的表观类型

TypeScript 中还有其他窄化类型保护,例如 in 运算符或 instanceof 运算符,您可以编写自己的 user-defined type guard or an assertion function 来缩小类型他们的投入。 None 其中对您有很大帮助;到目前为止,最好的解决方法就是不要重复使用相同的变量来表示两种不同的非联合类型,如您所知。