Skip to content

Commit

Permalink
推断的类型谓词
Browse files Browse the repository at this point in the history
5.5
  • Loading branch information
zhongsp committed Aug 10, 2024
1 parent 1f9ab31 commit cd9ee6d
Showing 1 changed file with 157 additions and 0 deletions.
157 changes: 157 additions & 0 deletions zh/release-notes/typescript-5.5.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# TypeScript 5.5

## 推断的类型谓词

TypeScript 的控制流分析在跟踪变量类型在代码中的变化时表现得非常出色:

```ts
interface Bird {
commonName: string;
scientificName: string;
sing(): void;
}

// Maps country names -> national bird.
// Not all nations have official birds (looking at you, Canada!)
declare const nationalBirds: Map<string, Bird>;

function makeNationalBirdCall(country: string) {
const bird = nationalBirds.get(country); // bird has a declared type of Bird | undefined
if (bird) {
bird.sing(); // bird has type Bird inside the if statement
} else {
// bird has type undefined here.
}
}
```

通过让你处理 `undefined` 情况,TypeScript 促使你编写更健壮的代码。

在过去,这种类型细化在数组上更难应用。在所有以前的 TypeScript 版本中,这都会是一个错误:

```ts
function makeBirdCalls(countries: string[]) {
// birds: (Bird | undefined)[]
const birds = countries
.map(country => nationalBirds.get(country))
.filter(bird => bird !== undefined);

for (const bird of birds) {
bird.sing(); // error: 'bird' is possibly 'undefined'.
}
}
```

代码是完全没有问题的:我们已经过滤掉了数组中所有的 `undefined` 值。
但是 TypeScript 却无法跟踪这些变化。

TypeScript 5.5 可以处理这种情况:

```ts
function makeBirdCalls(countries: string[]) {
// birds: Bird[]
const birds = countries
.map(country => nationalBirds.get(country))
.filter(bird => bird !== undefined);

for (const bird of birds) {
bird.sing(); // ok!
}
}
```

注意 `birds` 变量的更精确类型。

因为 TypeScript 现在能够为 `filter` 函数推断出[类型谓词](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates),所以这段代码才能工作。
你可以将代码提出到独立的函数中以便能清晰地看出这些:

```ts
// function isBirdReal(bird: Bird | undefined): bird is Bird
function isBirdReal(bird: Bird | undefined) {
return bird !== undefined;
}
```

`bird is Bird` 是类型谓词。
它表示,如果函数返回 `true`,那么结果为 `Bird` (如果函数返回 `false`,结果为 `undefined`)。
`Array.prototype.filter` 的类型声明能够识别类型谓词,所以最终的结果是你获得了一个更精确的类型,并且代码通过了类型检查器的验证。

如果以下条件成立,TypeScript 会推断一个函数返回一个类型谓词:

- 函数没有显式的返回类型或类型谓词注解。
- 函数只有一个返回语句,并且没有隐式返回。
- 函数不会改变其参数。
- 函数返回的布尔表达式与参数的类型细化有关。

通常,这种推断方式会如你所预期的那样工作。以下是一些推断类型谓词的更多示例:

```ts
// const isNumber: (x: unknown) => x is number
const isNumber = (x: unknown) => typeof x === 'number';

// const isNonNullish: <T>(x: T) => x is NonNullable<T>
const isNonNullish = <T,>(x: T) => x != null;
```

从前,TypeScript 仅会推断出这类函数返回 `boolean`
但现在会推断出带类型谓词的签名,例如 `x is number``x is NonNullable<T>`

类型谓词具有“当且仅当”的语义。
如果函数返回 `x is T`,那就意味着:

1. 如果函数返回 `true`,那么 `x` 的类型为 `T`
1. 如果函数返回 `false`,那么 `x` 的类型不为 `T`

如果你期待得到一个类型谓词但却没有,那么有可能违反了第二条规则。
这通常出现在“真值”检查中:

```ts
function getClassroomAverage(students: string[], allScores: Map<string, number>) {
const studentScores = students
.map(student => allScores.get(student))
.filter(score => !!score);

return studentScores.reduce((a, b) => a + b) / studentScores.length;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// error: Object is possibly 'undefined'.
}
```

TypeScript 没有为 `score => !!score` 推断出类型谓词,这是有道理的:如果返回 `true`,那么 `score` 是一个数字。但如果返回 `false`,那么 `score` 可能是 `undefined` 或者是数字(特别是 `0`)。这确实是一个漏洞:如果有学生在测试中得了零分,那么过滤掉他们的分数会导致平均分上升。这样一来,少数人会高于平均水平,而更多的人会感到沮丧!

与第一个例子一样,最好明确地过滤掉 `undefined` 值:

```ts
function getClassroomAverage(students: string[], allScores: Map<string, number>) {
const studentScores = students
.map(student => allScores.get(student))
.filter(score => score !== undefined);

return studentScores.reduce((a, b) => a + b) / studentScores.length; // ok!
}
```

当对象类型不存在歧义时,“真实性”检查会为对象类型推断出类型谓词。
请记住,函数必须返回一个布尔值才能成为推断类型谓词的候选者:`x => !!x` 可能会推断出类型谓词,但 `x => x` 绝对不会。

显式类型谓词依然像以前一样工作。TypeScript 不会检查它是否会推断出相同的类型谓词。
显式类型谓词("is")并不比类型断言("as")更安全。

如果 TypeScript 现在推断出的类型比你期望的更精确,那么这个特性可能会破坏现有的代码。例如:

```ts
// Previously, nums: (number | null)[]
// Now, nums: number[]
const nums = [1, 2, 3, null, 5].filter(x => x !== null);

nums.push(null); // ok in TS 5.4, error in TS 5.5
```

解决方法是使用显式类型注解告诉 TypeScript 你想要的类型:

```ts
const nums: (number | null)[] = [1, 2, 3, null, 5].filter(x => x !== null);
nums.push(null); // ok in all versions
```

更多详情请参考[PR](https://github.com/microsoft/TypeScript/pull/57465)[Dan 的博客](https://effectivetypescript.com/2024/04/16/inferring-a-type-predicate/)

0 comments on commit cd9ee6d

Please sign in to comment.