引言
2024年6月,TypeScript 5.5 正式发布,带来了多项令人兴奋的新特性。本文基于官方发布说明,深入解析这些新功能如何提升开发体验和代码质量。
一、推断类型谓词(Inferred Type Predicates)
1.1 问题背景
在 TypeScript 5.5 之前,数组过滤等操作中的类型收窄一直是个痛点:
typescript
const mixed = [1, "two", 3, "four", null];
const strings = mixed.filter(item => typeof item === "string");
for (const str of strings) {
str.toUpperCase(); // ❌ 错误:'str' 可能是 'undefined'
}
虽然我们已经过滤掉了非字符串值,但 TypeScript 无法理解这一点。
1.2 TypeScript 5.5 的解决方案
现在,TypeScript 可以自动推断类型谓词:
typescript
const mixed = [1, "two", 3, "four", null];
const strings = mixed.filter(item => typeof item === "string");
// strings 的类型:string[]
for (const str of strings) {
str.toUpperCase(); // ✅ 正常工作!
}
TypeScript 会自动推断 filter 函数返回 item is string 类型谓词。
1.3 推断规则
TypeScript 在以下条件下会推断类型谓词:
- 函数没有显式的返回类型或类型谓词注解
- 函数只有一个 return 语句,没有隐式返回
- 函数不修改其参数
- 函数返回与参数类型收窄相关的布尔表达式
示例:
typescript
// 自动推断:(x: unknown) => x is number
const isNumber = (x: unknown) => typeof x === 'number';
// 自动推断:<T>(x: T) => x is NonNullable<T>
const isNonNullish = <T,>(x: T) => x != null;
1.4 注意事项:避免"真值检查"陷阱
typescript
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;
// ❌ 错误:对象可能是 'undefined'
}
问题:!!score 对于 0 也会返回 false,导致学生得分为 0 时被错误过滤。
正确做法:
typescript
.filter(score => score !== undefined); // ✅ 明确过滤 undefined
二、常量索引访问的控制流收窄
TypeScript 5.5 现在可以收窄 obj[key] 形式的表达式类型:
typescript
function f1(obj: Record<string, unknown>, key: string) {
if (typeof obj[key] === "string") {
obj[key].toUpperCase(); // ✅ TypeScript 5.5 中正常工作
}
}
前提是 obj 和 key 都是常量(不会被修改)。
三、JSDoc @import 标签
3.1 问题
在 JavaScript 文件中导入类型一直很麻烦:
javascript
// ❌ 运行时错误!SomeType 不存在
import { SomeType } from "./some-module";
/**
* @param {SomeType} myValue
*/
function doSomething(myValue) {}
之前的解决方案:
javascript
/**
* @param {import("./some-module").SomeType} myValue
*/
function doSomething(myValue) {}
3.2 新的 @import 标签
TypeScript 5.5 引入了更简洁的语法:
javascript
/** @import { SomeType } from "some-module" */
/**
* @param {SomeType} myValue
*/
function doSomething(myValue) {}
也支持命名空间导入:
javascript
/** @import * as someModule from "some-module" */
/**
* @param {someModule.SomeType} myValue
*/
function doSomething(myValue) {}
四、正则表达式语法检查
TypeScript 5.5 开始检查正则表达式的语法错误:
typescript
let myRegex = /@robot(\s+(please|immediately)))? do some task/;
// ~
// ❌ 错误:意外的 ')'。是否想用反斜杠转义?
还能检测不存在的反向引用:
typescript
let myRegex = /@typedef \{import\((.+)\)\.([a-zA-Z_]+)\} \3/u;
// ~
// ❌ 错误:此反向引用指向不存在的组。
// 此正则表达式中只有 2 个捕获组。
以及命名捕获组:
typescript
let myRegex = /@typedef \{import\((?<importPath>.+)\)\.(?<importedEntity>[a-zA-Z_]+)\} \k<namedImport>/;
// ~~~~~~~~~~~
// ❌ 错误:此正则表达式中没有名为 'namedImport' 的捕获组。
五、ECMAScript Set 新方法支持
TypeScript 5.5 添加了对 ES 提案中 Set 新方法的类型声明:
typescript
let fruits = new Set(["apples", "bananas", "pears", "oranges"]);
let applesAndBananas = new Set(["apples", "bananas"]);
let oranges = new Set(["oranges"]);
// union(并集)
console.log(fruits.union(oranges));
// Set(4) {'apples', 'bananas', 'pears', 'oranges'}
// intersection(交集)
console.log(fruits.intersection(applesAndBananas));
// Set(2) {'apples', 'bananas'}
// difference(差集)
console.log(fruits.difference(oranges));
// Set(3) {'apples', 'bananas', 'pears'}
// symmetricDifference(对称差)
console.log(applesAndBananas.symmetricDifference(oranges));
// Set(3) {'apples', 'bananas', 'oranges'}
// isSubsetOf(子集判断)
console.log(applesAndBananas.isSubsetOf(fruits)); // true
// isSupersetOf(超集判断)
console.log(fruits.isSupersetOf(applesAndBananas)); // true
// isDisjointFrom(不相交判断)
console.log(applesAndBananas.isDisjointFrom(oranges)); // true
六、独立声明(Isolated Declarations)
6.1 背景
生成声明文件(.d.ts)通常需要完整的类型检查,这在大型项目中会成为瓶颈。
6.2 解决方案
新的 --isolatedDeclarations 选项要求开发者为导出的 API 显式添加类型注解:
typescript
// ❌ 错误
export function foo() {
return x;
}
// ✅ 正确
export function foo(): string {
return x;
}
这样,工具可以在不进行完整类型检查的情况下生成声明文件,实现:
- 更快的声明文件生成
- 并行构建:可以同时生成多个项目的声明文件
6.3 权衡
- 需要更多的类型注解
- 但换来了更快的构建速度和更好的并行化能力
七、性能优化
TypeScript 5.5 带来了显著的性能提升:
- 语言服务优化:编辑器操作快 10-20%
- 公共 API 优化:构建时间减少 5-8%
- transpileModule 优化:速度提升约 2 倍
- 包体积减少:从 30.2 MB 降至 20.4 MB(减少 32%)
八、其他改进
8.1 配置文件中的 ${configDir} 变量
解决共享 tsconfig.json 中相对路径的问题:
json
{
"compilerOptions": {
"typeRoots": [
"${configDir}/node_modules/@types",
"${configDir}/custom-types"
],
"outDir": "${configDir}/dist"
}
}
8.2 编辑器可靠性改进
- 配置文件错误的正确刷新
- 更好地处理文件删除后立即写入的情况
- 符号链接在失败解析中的跟踪
- 项目引用支持自动导入
总结
TypeScript 5.5 是一次重要的更新,主要亮点包括:
- 推断类型谓词:让数组过滤等操作的类型推断更智能
- JSDoc @import:改善 JavaScript 文件的类型导入体验
- 正则表达式检查:在编译时捕获正则错误
- 独立声明:为大型项目提供更快的构建选项
- 性能提升:全方位的速度和体积优化
这些改进让 TypeScript 在保持类型安全的同时,进一步提升了开发效率和构建性能。