• TypeScript 5.8都有哪些新特性?
  • 发布于 1周前
  • 60 热度
    0 评论
嘿,各位小伙伴们!TypeScript 5.8 已经发布一段时间了,最近我收到不少朋友的私信,都在问:"这次更新的新特性到底有啥用啊?"、"在什么场景下能派上用场?"今天,我就用最通俗易懂的语言,带大家一起扒一扒这些新特性的真面目,看看它们能如何在我们的日常开发中大显身手!不管你是 TypeScript 老司机还是新手上路,这篇文章都能帮你快速了解这次更新的精髓。咱们就从这些新特性解决了什么问题,以及在什么实际场景下能派上用场这两个方面来聊聊!

条件类型和索引访问类型的返回检查:告别类型混乱
问题场景
想象一下,你有一个根据选择模式返回不同类型的函数:
// 之前的写法
async function showQuickPick(
    prompt: string,
    selectionKind: SelectionKind,
    items: readonly string[],
): Promise<string | string[]> {
    // 实现代码
}
// 堆代码 duidaima.com
// 使用时需要手动检查类型
let result = await showQuickPick(
    "选择水果",
    SelectionKind.Multiple,
    ["苹果", "香蕉", "橙子"]
);

// 错误:无法确定 result 是 string 还是 string[]
console.log(`已选择: ${result.join(", ")}`);
TypeScript 5.8 的解决方案
现在,TypeScript 5.8 引入了更智能的条件类型推断机制:
type QuickPickReturn<S extends SelectionKind> =
    S extends SelectionKind.Multiple ? string[] : string;

asyncfunction showQuickPick<S extends SelectionKind>(
    prompt: string,
    selectionKind: S,
    items: readonly string[],
): Promise<QuickPickReturn<S>> {
    // 实现代码
}

// TypeScript 自动推断正确的返回类型
let multipleResult = await showQuickPick(
    "选择水果",
    SelectionKind.Multiple,
    ["苹果", "香蕉", "橙子"]
); // 类型为 string[]

let singleResult = await showQuickPick(
    "选择一个水果",
    SelectionKind.Single,
    ["苹果", "香蕉", "橙子"]
); // 类型为 string

// 现在可以安全地使用 join 方法
console.log(`已选择: ${multipleResult.join(", ")}`);
这种改进不仅让代码更加简洁,还在编译时就能捕获潜在的类型错误,大大提高了代码的可靠性。

CommonJS 终于可以 require() ESM 模块了!
长期以来,Node.js 生态系统中的一个主要痛点是 CommonJS 和 ECMAScript 模块(ESM)之间的互操作性问题:
1.ESM 文件可以导入 CommonJS 文件 ✅

2.CommonJS 文件不能 require() ESM 文件 ❌ 


这给库作者带来了巨大挑战,他们要么放弃对 CommonJS 用户的兼容性,要么选择"双重发布"(为 ESM 和 CommonJS 提供单独的入口点),或者干脆停留在 CommonJS 上。但,这一次模块互操作性有重大突破!


Node.js 22 的突破
Node.js 22 放宽了这一限制,允许从 CommonJS 模块中使用 require() 调用 ECMAScript 模块(虽然仍不支持包含顶级 await 的 ESM 文件)。

TypeScript 5.8 的支持
TypeScript 5.8 在 --module nodenext 标志下正式支持这一行为:
// 在 --module nodenext 下,以下代码不再报错
const esmModule = require('./myEsmModule.mjs');
这对库作者来说是个重大利好,他们现在可以在不进行复杂的双重发布的情况下提供 ESM 支持。

如何使用?
.如果你的项目运行在 Node.js 22 及更高版本,使用 --module nodenext

.对于库作者和旧版 Node.js 用户,建议继续使用 --module node16 或升级到 --module node18


--erasableSyntaxOnly 选项:与 Node.js 无缝协作
Node.js 23.6 引入了 --experimental-strip-types 模式,允许直接运行 TypeScript 文件,但要求所有 TypeScript 特有的语法都不能有运行时语义。换句话说,必须能够轻松地从文件中删除任何 TypeScript 特有的语法,留下一个有效的 JavaScript 文件。
不支持的结构包括:
.enum 声明
.含有运行时代码的命名空间和模块
.类中的参数属性

.导入别名


TypeScript 5.8 的解决方案
为了帮助开发者确保代码与 Node.js 的这一新功能兼容,TypeScript 5.8 引入了 --erasableSyntaxOnly 标志:
// 启用 --erasableSyntaxOnly 后
class Point {
    constructor(public x: number, public y: number) { }
    //          ~~~~~~~~~~~~~~~~
    // 错误! 当启用 'erasableSyntaxOnly' 时,不允许使用这种语法。
}
通过启用这个标志,你可以在编译时就发现潜在的兼容性问题,而不是等到运行时才遇到错误。

声明文件中的计算属性名保留:提升类型准确性
在之前的 TypeScript 版本中,当在类中使用计算属性名时,生成的声明文件可能会出现不一致的问题。TypeScript 5.8 对此进行了改进,现在会在声明文件中保留计算属性名,使其与实际代码保持一致。
之前的问题
export let propName = "answer";

exportclass MyClass {
    [propName] = 42;
}

// 生成的声明文件:
exportdeclarelet propName: string;
exportdeclareclass MyClass {
    [x: string]: number; // 泛化为索引签名
}
TypeScript 5.8 的改进
export let propName = "answer";

exportclass MyClass {
    [propName] = 42;
}

// 生成的声明文件:
exportdeclarelet propName: string;
exportdeclareclass MyClass {
    [propName]: number; // 保留了原始的计算属性名
}
这一改进使得声明文件更加准确,提高了类型检查的精确度,特别是在使用复杂对象结构时。

性能优化:更快的程序加载与更新
TypeScript 5.8 在性能方面也带来了显著改进,特别是对大型项目的支持:
路径规范化优化
在处理大量文件时,TypeScript 现在避免了不必要的数组分配,直接操作路径索引,减少了重复工作。

配置验证缓存
在编辑过程中,如果项目的基本结构没有变化,TypeScript 将避免重新验证配置选项,从而提高大项目的编辑响应速度。这些优化对于大型项目尤其重要,可以显著提升开发体验,减少等待时间。

6. 其他重要更新
.新增 --libReplacement 标志:
允许开发者禁用默认的库文件替换行为,提供更多自定义选项。 导入断言变更
导入断言变更 在 --module nodenext 中,TypeScript 推荐使用 with 替代 assert 进行导入断言。
// 不推荐
import data from "./data.json" assert { type: "json" };
// 推荐
import data from "./data.json" with { type: "json" };
DOM 类型更新 lib.d.ts 文件进行了更新,可能会影响依赖 DOM 类型的代码库。
如何升级到 TypeScript 5.8?
升级到最新版本非常简单:
npm install -D typescript@latest
如果你使用的是 Visual Studio Code,也可以更新 TypeScript 工具来体验这些新特性。

实际应用案例:提升开发效率的真实例子
案例一:API 客户端库的类型安全
假设你正在开发一个 API 客户端库,需要根据不同的请求参数返回不同的响应类型:
// TypeScript 5.8 之前
asyncfunction fetchData(endpoint: string, options?: RequestOptions): Promise<any> {
    // 实现代码
}

// 使用时需要手动类型断言
const userData = await fetchData('/users') as User[];
const productData = await fetchData('/products') as Product[];

// TypeScript 5.8
asyncfunction fetchData<T extends Endpoint>(
    endpoint: T,
    options?: RequestOptions
): Promise<EndpointResponse<T>> {
    // 实现代码
}

// 自动推断正确的返回类型
const userData = await fetchData('/users'); // 类型为 User[]
const productData = await fetchData('/products'); // 类型为 Product[]
这种改进不仅减少了代码量,还提高了类型安全性,避免了手动类型断言可能带来的错误。

案例二:库作者的模块兼容性
对于库作者来说,TypeScript 5.8 的 ESM/CommonJS 互操作性改进意味着可以简化发布流程:
// 之前需要维护两套入口点
// package.json
{
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"exports": {
    "import": "./dist/esm/index.js",
    "require": "./dist/cjs/index.js"
  }
}

// 现在可以简化为
{
"type": "module",
"main": "dist/index.js",
"exports": "./dist/index.js"
}
这大大减少了维护成本,同时提高了代码的一致性。

未来展望:TypeScript 5.9 已在路上
TypeScript 团队已经开始规划下一个版本——TypeScript 5.9。如果你对最新的开发感兴趣,可以通过以下命令安装每日构建版本:
npm install typescript@next

为什么推荐升级到 TypeScript 5.8
TypeScript 5.8 带来的改进不仅仅是表面的功能增强,而是对开发体验的全面提升:
1.更智能的类型推断:减少手动类型断言,提高代码可靠性
2.模块互操作性突破:解决 ESM 和 CommonJS 之间的长期痛点
3.与 Node.js 更好的集成:通过 --erasableSyntaxOnly 确保代码兼容性
4.更准确的声明文件:计算属性名保留提升类型准确性 显著的性能优化:特别是对大型项目的支持
用户评论