类型谓词推断:TS 5.5 中的意外之举

原文信息: 查看原文查看原文

Type Predicate Inference: The TS 5.5 Feature No One Expected

- Matt Pocock

TypeScript 5.5 将在接下来的几个月内发布。特别感谢一位贡献者的 PR,我已经知道这将是一个令人难以置信的版本。

上周五,TypeScript 合并了 Dan Vanderkam 的 PR,以"使用控制流分析从函数体中推断类型谓词"。让我们详细解释一下这个 PR 改变了什么,以及为什么这是一件大事。

问题

假设你想检查一个值是字符串还是数字。这很简单 - 你可以使用一个 if 语句:

console.log(value);
         const value: string | number

if (typeof value === "string") {
  console.log(value);
           const value: string
}

现在,在 if 语句内部,TypeScript 知道 value 是一个字符串。这称为"缩小" - TypeScript 已经将 value 的类型从 string | number 缩小为仅 string

但是,如果你想编写一个执行此操作的函数呢?你可能会写出这样的东西:

function isString(value: unknown) {
  return typeof value === "string";
}

然后,你将该函数应用到 value 上 - 但是有些不对劲:

if (isString(value)) {
  console.log(value); // string | number
}

我们失去了缩小!TypeScript 不知道 isString 是一个类型谓词 - 一个可以缩小其参数类型的函数。

我们可以通过给 isString 添加返回类型注释来修复这个问题:

function isString(value: unknown): value is string {
  return typeof value === "string";
}

if (isString(value)) {
  console.log(value);
           const value: string
}

现在,我们恢复了我们的缩小。我们甚至可以在 filter 调用中使用这个函数:

const arr = [1, "hello", 3, "world"];

const strings = arr.filter(isString);
        const strings: string[]

但这有点麻烦。更糟糕的是,返回类型注释可能与函数的实现不同步。我们可以将返回类型更改为 value is number,TypeScript 不会抱怨:

function isString(value: unknown): value is number {
  return typeof value === "string";
}

if (isString(value)) {
  console.log(value);
           const value: number
}

这使得这个注释相当脆弱。

如果 value is string 能够从函数体中推断出来,那不是很好吗?这样,它将与函数的实现保持同步,我们就不必自己写出来了。

解决方案

这正是 Dan 的 PR 所做的。在 TypeScript 5.5 中,你可以编写一个 isString 函数,它会从函数体中推断出类型谓词:

function isString(value: unknown) {
  return typeof value === "string";
}

if (isString(value)) {
  console.log(value); // string
}

这使用了 TypeScript 的控制流分析来从函数体中推断出类型谓词。这是用于在 if 语句内缩小类型的相同分析。

这意味着你可以在不必自己写出类型谓词的情况下进行复杂的类型缩小。

function isObjAndHasIdProperty(value: unknown) {
  return (
    typeof value === "object" &&
    value !== null &&
    "id" in value &&
    typeof value.id === "number"
  );
}

if (isObjAndHasIdProperty(value)) {
  console.log(value.id); // number
}

这对于 TypeScript 是一个巨大的胜利。它解决了 TypeScript 推断中长期存在的问题。它使得在函数中捕获缩小逻辑变得更容易。并且使得 TypeScript 的类型系统更加强大。

说实话,我简直不敢相信他们发布了这个功能。这是对 TypeScript 工作方式的巨大改变,它将使许多人的开发生活更加轻松。

filter(Boolean) 能工作吗?

当我在推特上发推时,有一个问题是关于 filter(Boolean) 是否会自动推断。

我需要在发布测试版时进行测试,但我认为不会。Boolean 本身不是类型谓词。例如,如果你尝试在 if 语句中使用它来移除联合中的 null,TypeScript 将不会推断出类型:

if (Boolean(value)) {
  console.log(value);
              const value: string | null
}

因此,它也不会与 .filter 一起工作。这意味着你仍然需要使用我的库 ts-reset 来获得正确的推断:

import "@total-typescript/ts-reset";

const arr = [null, "hello", null, "world"];

const strings = arr.filter(Boolean); // string[]
分享于 2024-03-23

访问量 177

预览图片