diff --git a/zh/release-notes/typescript-5.5.md b/zh/release-notes/typescript-5.5.md new file mode 100644 index 00000000..c226acf0 --- /dev/null +++ b/zh/release-notes/typescript-5.5.md @@ -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; + +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: (x: T) => x is NonNullable +const isNonNullish = (x: T) => x != null; +``` + +从前,TypeScript 仅会推断出这类函数返回 `boolean`。 +但现在会推断出带类型谓词的签名,例如 `x is number` 或 `x is NonNullable`。 + +类型谓词具有“当且仅当”的语义。 +如果函数返回 `x is T`,那就意味着: + +1. 如果函数返回 `true`,那么 `x` 的类型为 `T`。 +1. 如果函数返回 `false`,那么 `x` 的类型不为 `T`。 + +如果你期待得到一个类型谓词但却没有,那么有可能违反了第二条规则。 +这通常出现在“真值”检查中: + +```ts +function getClassroomAverage(students: string[], allScores: Map) { + 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) { + 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/)。