网易首页 > 网易号 > 正文 申请入驻

TypeScript 5.7:一大波新特性来袭!

0
分享至

最近TypeScript 5.7发布了 RC 版本,其中包括了一大波新特性和优化措施,下面我们一起来学习下。

在 TypeScript 中,对于未初始化的变量,长期以来编译器已经可以捕获到一些问题了,特别是在所有前置分支中变量尚未初始化的情况下。

例如,对于下面这段代码:

let result: number;
if (someCondition()) {
    result = doSomeWork();
} else {
    let temporaryWork = doSomeWork();
    temporaryWork *= 2;
     // 忘记给 result 赋值了 
}

console.log(result);  // error: Variable 'result' is used before being assigned.

在这段代码中,由于result在所有可能的路径中并没有被保证初始化,因此编译器会报错,这一点 TypeScript 已经支持了很长时间。

但是,在有些情况下,分析就不那么准确了。例如,当变量在一个单独的函数中被访问时,类型系统无法确定该函数何时会被调用,往往会乐观地认为变量已经被初始化。

以下是这样的例子:

function foo() {
    let result: number;
    if (someCondition()) {
        result = doSomeWork();
    } else {
        let temporaryWork = doSomeWork();
        temporaryWork *= 2;
         // 忘记给 result 赋值了 
    }

    printResult();

    function printResult() {
        console.log(result);  // 这里不会报错 
    }
}

在上述代码中,虽然printResult函数内部使用了result变量,但由于类型系统无法判断printResult何时会被调用,它会默认认为result已经被初始化,而不会报错。

但是,在 TypeScript 5.7 中,即使变量可能已经被初始化,类型系统也会在变量完全未初始化的情况下报告错误。来看以下例子:

function foo() {
    let result: number;
    
     // 执行操作,但忘记给 result 赋值 

    function printResult() {
        console.log(result);  // error: Variable 'result' is used before being assigned. 
    }
}

在这段代码中,由于result在任何执行路径中都没有被赋值,类型系统会明确报错,指出未被初始化的变量使用了,这样可以帮助开发者更早发现程序中的潜在问题。

相对路径重写

现在有许多工具和运行时支持直接运行 TypeScript 代码,这意味着它们不需要生成输出 JavaScript 文件的构建步骤。例如,ts-nodetsxDenoBun都支持直接运行.ts文件。最近,Node.js 也在探讨这种支持,例如通过--experimental-transform-types--experimental-strip-types选项。这样做非常方便,因为它让我们可以更快速地迭代,而无需担心重新运行构建任务。

但是,当使用这些模式时,有一些复杂性需要注意。为了最大限度地兼容所有这些工具,直接导入的 TypeScript 文件必须在运行时使用适当的 TypeScript 扩展名。例如,要导入一个名为foo.ts的文件,我们需要在 Node.js 的新实验性支持中这样写:

// main.ts 

import * as foo from "./foo.ts";  // <- 这里需要 foo.ts,而不是 foo.js

通常情况下,如果我们这样写,TypeScript 会报错,因为它期望我们导入的是输出文件。由于有些工具允许.ts导入,TypeScript 早已通过一个名为--allowImportingTsExtensions的选项支持这种导入方式。这种做法虽然可行,但如果我们需要真正将这些.ts文件生成.js文件呢?这对于那些需要分发.js文件的库作者来说是一个需求。但在此之前,TypeScript 一直避免重写任何路径。

为了支持这种情况,我们新增了一个编译选项--rewriteRelativeImportExtensions。当导入路径是相对路径(以./../开头),以 TypeScript 扩展名(.ts.tsx.mts.cts)结尾,并且是非声明文件时,编译器会将路径重写为相应的 JavaScript 扩展名(.js.jsx.mjs.cjs)。

// 在使用 --rewriteRelativeImportExtensions 选项时... 

 // 这些路径会被重写。 
import * as foo from "./foo.ts";
import * as bar from "../someFolder/bar.mts";

 // 这些路径不会以任何方式被重写。 
import * as a from "./foo";
import * as b from "some-package/file.ts";
import * as c from "@some-scope/some-package/file.ts";
import * as d from "#/file.ts";
import * as e from "./file.js";

以上的特性让我们可以编写 TypeScript 代码 "原地" 运行,并在准备好后将其编译为 JavaScript。

需要注意的是,TypeScript 通常避免重写路径。原因有很多,但最明显的一个原因是动态导入。如果开发者写了如下代码,处理导入接收的路径就不是那么简单的事了。实际上,不可能在任何依赖项中重写import的行为。

function getPath() {
    if (Math.random() < 0.17) {
        return "./foo.ts";
    } else {
        return "./foo.js";
    }
}

let myImport = await import(getPath());

还有一个问题是(如上所述)只有相对路径会被重写,并且它们是“天真地”被重写。这意味着任何依赖 TypeScript 的baseUrlpaths的路径将不会被重写:

// tsconfig.json 

{
    "compilerOptions": {
        "module": "nodenext",
         // ... 
        "paths": {
            "@/*": ["./src/*"]
        }
    }
}

// 不会被转换,不会生效。 
import * as utilities from "@/utilities.ts";

同样,任何可能通过package.jsonexportsimports字段解析的路径也不会被重写:

// package.json 
{
    "name": "my-package",
    "imports": {
        "#root/*": "./dist/*"
    }
}

// 不会被转换,不会生效。 
import * as utilities from "#root/utilities.ts";

因此,如果你一直在使用带有多个包相互引用的工作空间式布局,可能需要使用带有作用范围的条件导出,以使其生效:

// my-package/package.json 

{
    "name": "my-package",
    "exports": {
        ".": {
            "@my-package/development": "./src/index.ts",
            "import": "./lib/index.js"
        },
        "./*": {
            "@my-package/development": "./src/*.ts",
            "import": "./lib/*.js"
        }
    }
}

每次你想要导入.ts文件时,可以使用node --conditions=@my-package/development运行。

注意我们为条件使用的作用域@my-package/development。这是为了避免与可能也使用development条件的依赖项发生冲突而做出的权宜之计。如果每个包都在他们的包里导出development,那么解析过程可能会尝试解析.ts文件,这不一定能成功。

支持 --target es2024 和 --lib es2024

TypeScript 5.7 现在支持--target es2024,这意味着用户可以将目标运行时定为 ECMAScript 2024。这一特性主要是为了启用新的--lib es2024选项,该选项包含了许多新特性,例如SharedArrayBufferArrayBufferObject.groupByMap.groupByPromise.withResolvers等等。此外,Atomics.waitAsync也从--lib es2022移动到了--lib es2024

需要注意的是,由于 SharedArrayBuffer 和 ArrayBuffer 的变化,它们现在有些不同了。为了弥合这个差距并且保留底层的缓冲区类型,所有类型化数组(例如Uint8Array等)现在也变成了泛型。

interface Uint8Array
      
 extends ArrayBufferLike = ArrayBufferLike> {      // ... }

每个类型化数组现在包含一个名为TArrayBuffer的类型参数,虽然这个类型参数有一个默认的类型参数,这样我们仍然可以使用Int32Array而不必显式地写成Int32Array

上层配置文件搜索

当使用 TSServer(如 Visual Studio 或 VS Code)在编辑器中加载 TypeScript 文件时,编辑器会尝试找到 "拥有" 该文件的相关tsconfig.json文件。为此,它会从正在编辑的文件所在的目录开始向上遍历目录树,寻找任何名为tsconfig.json的文件。

以前,这个搜索会在找到第一个tsconfig.json文件时停止;但是,想象一下如下的项目结构:

project/
├── src/
│   ├── foo.ts
│   ├── foo-test.ts
│   ├── tsconfig.json
│   └── tsconfig.test.json
└── tsconfig.json

在这个例子中,src/tsconfig.json是项目的主要配置文件,而src/tsconfig.test.json是用于运行测试的配置文件。

// src/tsconfig.json 
{
    "compilerOptions": {
        "outDir": "../dist"
    },
    "exclude": ["**/*.test.ts"]
}

// src/tsconfig.test.json 
{
    "compilerOptions": {
        "outDir": "../dist/test"
    },
    "include": ["**/*.test.ts"],
    "references": [
        { "path": "./tsconfig.json" }
    ]
}

// tsconfig.json 
{
    "files": [],
    "references": [
        { "path": "./src/tsconfig.json" },
        { "path": "./src/tsconfig.test.json" }
    ]
}

问题在于,当编辑foo-test.ts时,编辑器会找到project/src/tsconfig.json作为 "所有" 配置文件,但这并不是我们想要的。如果搜索在这里停止,这可能不是理想的结果。以前,唯一的解决办法是将src/tsconfig.json重命名为src/tsconfig.src.json,这样所有文件都会找到顶层的tsconfig.json,它引用了所有可能的项目。

project/
├── src/
│   ├── foo.ts
│   ├── foo-test.ts
│   ├── tsconfig.src.json
│   └── tsconfig.test.json
└── tsconfig.json

为了不让开发者必须这样做,TypeScript 5.7 现在会继续向上遍历目录树,在编辑器场景中找到其他合适的tsconfig.json文件。这样可以为项目的组织和配置文件的结构提供更多的灵活性。

在编辑器中更快地检查复杂项目的归属

想象一下,我们有一个大型代码库,结构如下:

packages
├── graphics/
│   ├── tsconfig.json
│   └── src/
│       └── ...
├── sound/
│   ├── tsconfig.json
│   └── src/
│       └── ...
├── networking/
│   ├── tsconfig.json
│   └── src/
│       └── ...
├── input/
│   ├── tsconfig.json
│   └── src/
│       └── ...
└── app/
    ├── tsconfig.json
    ├── some-script.js
    └── src/
        └── ...

每个packages目录中的文件夹都是一个独立的 TypeScript 项目,app目录是主项目,依赖于所有其他项目。

// app/tsconfig.json 
{
    "compilerOptions": {
         // ... 
    },
    "include": ["src"],
    "references": [
        { "path": "../graphics/tsconfig.json" },
        { "path": "../sound/tsconfig.json" },
        { "path": "../networking/tsconfig.json" },
        { "path": "../input/tsconfig.json" }
    ]
}

注意我们在app目录中有一个文件some-script.js。当我们在编辑器中打开some-script.js时,TypeScript 语言服务(也处理 JavaScript 文件的编辑器体验!)需要确定该文件属于哪个项目,以便应用正确的设置。

在这种情况下,最近的tsconfig.json并未包含some-script.js,但 TypeScript 将继续询问:“app/tsconfig.json 引用的某个项目是否包含some-script.js?”为了做到这一点,以前 TypeScript 会逐一加载每个项目,并在找到包含some-script.js的项目时停止。即使some-script.js不包含在根文件集中,TypeScript 仍会解析项目中的所有文件,因为根文件集中的某些文件可能间接引用some-script.js

在长时间的实践中,我们发现这种行为会在大型代码库中导致极端且不可预测的行为。开发人员会打开偶然的脚本文件,但发现整个代码库都被加载了,等待时间异常漫长。

幸运的是,任何可以被另一个(非工作区)项目引用的项目必须启用一个名为composite的标志,该标志强制要求所有输入源文件必须在创建项目时已知。因此,当探测复合项目时,TypeScript 5.7 只会检查文件是否属于该项目的根文件集。这应能避免常见的最坏情况行为的发生。

--module nodenext 模式下的 JSON 模块导入验证

--module nodenext模式下从.json文件导入时,TypeScript 现在会强制执行某些规则,以防止运行时错误。

例如,导入 JSON 文件时,必须包含type: "json"的导入属性:

import myConfig from "./myConfig.json";
 //                   ~~~~~~~~~~~~~~~~~ 
 // ❌ error: Importing a JSON file into an ECMAScript module requires a 'type: "json"' import attribute when 'module' is set to 'NodeNext'. 

import myConfig from "./myConfig.json" with { type: "json" };
 //                                          ^^^^^^^^^^^^^^^^ 
 // ✅ 这样是正确的,因为我们提供了 `type: "json"`

除此之外,TypeScript 不会生成 "具名" 导出,JSON 导入的内容只能通过默认导出来访问。

// ✅ 这样是可以的: 
import myConfigA from "./myConfig.json" with { type: "json" };
let version = myConfigA.version;

 /////////// 

import * as myConfigB from "./myConfig.json" with { type: "json" };

 // ❌ 这样是不行的: 
let version = myConfig.version;

 // ✅ 这样是可以的: 
let version = myConfig.default.version;
支持 Node.js 中的 V8 编译缓存

Node.js 22 支持一个新 APImodule.enableCompileCache()。这个 API 允许运行时重用第一次运行工具后的解析和编译工作的一部分。

TypeScript 5.7 现在利用了这个 API,使其能够更快地开始做有用的工作。在我们自己的一些测试中,我们发现运行tsc --version的速度提升了约 2.5 倍。

Benchmark 1: node ./built/local/_tsc.js --version (*without* caching)
  Time (mean ± σ):     122.2 ms ±   1.5 ms    [User: 101.7 ms, System: 13.0 ms]
  Range (min … max):   119.3 ms … 132.3 ms    200 runs
 
Benchmark 2: node ./built/local/tsc.js --version  (*with* caching)
  Time (mean ± σ):      48.4 ms ±   1.0 ms    [User: 34.0 ms, System: 11.1 ms]
  Range (min … max):    45.7 ms …  52.8 ms    200 runs
 
Summary
  node ./built/local/tsc.js --version ran
    2.52 ± 0.06 times faster than node ./built/local/_tsc.js --version
TypedArrays 现在是ArrayBufferLike的泛型

在 ECMAScript 2024 中,SharedArrayBufferArrayBuffer的类型略有不同。为了弥合这一差距并保留底层的缓冲类型,所有类型化数组(如Uint8Array等)现在也是泛型。

interface Uint8Array
      
 extends ArrayBufferLike = ArrayBufferLike> {      // ... }

每个类型化数组现在都包含一个名为TArrayBuffer的类型参数,尽管该类型参数有一个默认类型参数,因此用户仍然可以直接使用Int32Array,而不必显式地写成Int32Array

从非字面量方法名创建类中的索引签名

TypeScript 现在对于使用非字面量计算属性名声明的方法在类中具有更一致的行为。例如:

declare const symbolMethodName: symbol;

export class A {
    [symbolMethodName]() { return 1 };
}

以前,TypeScript 会将这个类视为如下:

export class A {
}

换句话说,从类型系统的角度来看,[symbolMethodName]对类A没有任何影响。

TypeScript 5.7 现在更有意义地看待方法[symbolMethodName](),并生成一个索引签名。因此,上面的代码现在解释为如下代码:

export class A {
    [x: symbol]: () => number;
}

这为对象字面量中的属性和方法提供了一致的行为。

在返回nullundefined的函数上增加隐式any错误

当一个函数表达式被上下文类型化为返回泛型类型的签名时,如果没有启用strictNullChecks,TypeScript 现在会根据noImplicitAny适当地提供隐式any错误。

declare var p: Promise

 ; const p2 = p.catch(() => null); //                 ~~~~~~~~~~ // error TS7011: Function expression, which lacks return-type annotation, implicitly has an 'any' return type.

这意味着如果一个函数返回nullundefined,但未明确指定返回类型,则会触发错误。这样可以确保代码更加严格和健壮,减少因不明确的类型导致的潜在问题。

最后

参考:https://devblogs.microsoft.com/typescript/announcing-typescript-5-7-rc/

点赞在看是最大的支持⬇️❤️⬇️

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
U17国足喜获上上签,进世界杯概率超5成,董路:拉肚子都能赢印尼

U17国足喜获上上签,进世界杯概率超5成,董路:拉肚子都能赢印尼

绿茵舞着
2026-02-13 00:08:13
终 于成功了!我国向世界宣布好消息!美曰慌了:这怎么可能!

终 于成功了!我国向世界宣布好消息!美曰慌了:这怎么可能!

趣生活
2026-02-12 21:05:47
小米宣布:YU7拿下1月国内乘用车销冠!无任何定语

小米宣布:YU7拿下1月国内乘用车销冠!无任何定语

CNMO科技
2026-02-12 14:26:09
首次公开!过去一年,我靠这15个超给力AI搞钱工具,多赚2倍钱!

首次公开!过去一年,我靠这15个超给力AI搞钱工具,多赚2倍钱!

毯叔盘钱
2026-02-11 21:45:23
博主吐槽被拒载于是自己买车载狗,看到当事狗后,网友集体反水:你知道的,我很少站司机这边除了这次!

博主吐槽被拒载于是自己买车载狗,看到当事狗后,网友集体反水:你知道的,我很少站司机这边除了这次!

爱宠物
2026-02-12 21:48:37
你身边有把一手好牌打的稀烂的人吗?网友:都是上辈子欠人家的

你身边有把一手好牌打的稀烂的人吗?网友:都是上辈子欠人家的

带你感受人间冷暖
2026-01-21 00:15:05
回顾“91女神”琪琪:五官出众,却因天真让自己“受伤”

回顾“91女神”琪琪:五官出众,却因天真让自己“受伤”

就一点
2025-11-22 10:36:39
国家出手,一场影响千万家庭的中考改革,正式提速

国家出手,一场影响千万家庭的中考改革,正式提速

深蓝夜读
2026-02-12 10:18:21
一眼沦陷!183cm世界小姐中国总冠军陈蕾娜闪耀全场

一眼沦陷!183cm世界小姐中国总冠军陈蕾娜闪耀全场

可乐谈情感
2026-02-12 20:06:51
上海炒股大赛冠军的箴言:如果手里只有10万,不妨死磕"七大口诀"

上海炒股大赛冠军的箴言:如果手里只有10万,不妨死磕"七大口诀"

一方聊市
2026-01-19 13:13:48
张一鸣以一己之力单挑整个互联网

张一鸣以一己之力单挑整个互联网

磐石之心
2026-02-12 12:01:31
最孤独婚车后续:新郎新娘颜值高,别克正式回应大格局送上祝福

最孤独婚车后续:新郎新娘颜值高,别克正式回应大格局送上祝福

社会日日鲜
2026-02-12 12:33:40
中央定调!多省火速跟进,原拆原建不再普惠,老破小迎来严格筛选

中央定调!多省火速跟进,原拆原建不再普惠,老破小迎来严格筛选

万物知识圈
2026-02-12 07:37:41
垃圾不够烧?真相可能打脸了

垃圾不够烧?真相可能打脸了

大道微言
2026-02-10 19:40:32
内蒙古一200斤男子欠5000万不还,被债主装进铁笼沉入80米水库,谁料,2年后才被捞出...

内蒙古一200斤男子欠5000万不还,被债主装进铁笼沉入80米水库,谁料,2年后才被捞出...

品读时刻
2026-02-11 17:18:30
直降1500元!华为Mate新机突然降价:2月12日,正式官降!

直降1500元!华为Mate新机突然降价:2月12日,正式官降!

科技堡垒
2026-02-12 12:30:33
夸美国空气香甜的杨舒平,已被美驱逐出境,如今回国下场怎么样了

夸美国空气香甜的杨舒平,已被美驱逐出境,如今回国下场怎么样了

谈史论天地
2026-02-07 13:20:03
贵州妹子在富士康干活,没事用没做完的苹果手机拍照,拍完忘删了

贵州妹子在富士康干活,没事用没做完的苹果手机拍照,拍完忘删了

百态人间
2026-02-12 15:29:08
回顾:上海杀妻案朱晓东被处死刑,狱中对妻子的评价,让人胆寒

回顾:上海杀妻案朱晓东被处死刑,狱中对妻子的评价,让人胆寒

谈史论天地
2026-02-11 13:30:11
汉人最伟大的巅峰之战!若此战失败,中国或将分裂成几十个小国

汉人最伟大的巅峰之战!若此战失败,中国或将分裂成几十个小国

千秋文化
2026-01-06 20:49:21
2026-02-13 07:24:49
开源中国 incentive-icons
开源中国
每天为开发者推送最新技术资讯
7596文章数 34500关注度
往期回顾 全部

科技要闻

10倍速的一夜:三大模型春节前的暗战

头条要闻

钟南山:会用证据让全世界服气

头条要闻

钟南山:会用证据让全世界服气

体育要闻

31岁首次参加冬奥,10年前她是个水管工

娱乐要闻

《惊蛰无声》违规抢占排片遭影院控诉

财经要闻

“影子万科”如何掘金万科?

汽车要闻

开212 T01柴油版去穿越 连牧马人都跟不上

态度原创

健康
亲子
本地
游戏
公开课

转头就晕的耳石症,能开车上班吗?

亲子要闻

孩子最渴望听到父母说的五句话

本地新闻

下一站是嘉禾望岗,请各位乘客做好哭泣准备

因不满《守望先锋》女角色太好看,外网想把她改成刻板印象眯眯眼"/> 主站 商城 论坛 自运营 登录 注册 因不满《守望先锋》女角色太好看,外网想把她改成...

公开课

李玫瑾:为什么性格比能力更重要?

无障碍浏览 进入关怀版