5.2 Iteration Plan:https://github.com/microsoft/TypeScript/issues/54298
$ npm install typescript@beta
JavaScript and TypeScript Nightly:https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-typescript-next
由于 beta 版本与正式版本通常不会有明显的差异,这一系列通常只会介绍 beta 版本而非正式版本。

rbuckton:https://github.com/rbuckton proposal-explicit-resource-management:https://github.com/tc39/proposal-explicit-resource-management
实际上,所谓的“显式”对应的是此前如 WeakSet 与 WeakMap 这样,会由 JS 运行时作为垃圾回收进行的“隐式”工作。显式资源管理意味着,用户可以主动声明块级作用域内依赖的资源,通过 Symbol.disposable 这样的命令式或 using
这样的声明式,然后在离开作用域时就能够由运行时自动地释放这些标记的资源。
Symbol.disposable:https://github.com/zloirock/core-js/blob/901efc66ecfd612f107976ec6a2cd6f426b70975/packages/core-js/modules/esnext.symbol.dispose.js
function * g() {
const handle = acquireFileHandle();
try {
...
}
finally {
handle.release(); // 释放资源
}
}
const obj = g();
try {
const r = obj.next();
...
}
finally {
obj.return(); // 显式调用函数 g 中的 finally 代码块
}
export function doSomeWork() {
const path = ".some_temp_file";
const file = fs.openSync(path, "w+");
try {
if (someCondition()) {
return;
}
}
finally {
fs.closeSync(file);
fs.unlinkSync(path);
}
}
using
关键字,来声明仅在当前作用域内使用的资源:
function * g() {
using handle = acquireFileHandle(); // 定义与代码块关联的资源
}
{
using obj = g(); // 显式声明资源
const r = obj.next();
} // 自动调用释放逻辑
{
using handlerSync = openSync();
await using handlerSync = openAsync(); // 同样支持异步的资源声明
} // 自动调用释放逻辑
using
关键字并不是语言底层的新功能,它更像是语法糖,比如我们看 Babel 插件的编译结果:
// 输入
using handlerSync = openSync();
await using handlerAsync = await openAsync();
// 输出
try {
var _stack = [];
var handlerSync = babelHelpers.using(_stack, openSync());
var handlerAsync = babelHelpers.using(_stack, await openAsync(), true);
} catch (_) {
var _error = _;
var _hasError = true;
} finally {
await babelHelpers.dispose(_stack, _error, _hasError);
}

随着 22 年 3 月的 TC39 会议上,新版装饰器终于迈入到 Stage 3 阶段,TypeScript 在 5.0 版本中也提供了对应于提案的实现。而在 23 年 5 月的 TC39 会议上,装饰器的好朋友元数据对应的提案也进入到了 Stage 3 阶段(需要注意的是,此前我们使用的元数据能力,实际上来自于反射元数据 reflect-metadata 提案以及它提供的 Polyfill 能力,而本次的 Decorator Metadata 提案则是一个全新的提案,甚至它的仓库都是在装饰器提案迈入到 Stage 3 后创建的)。
reflect-metadata:https://github.com/rbuckton/reflect-metadata?spm=ata.21736010.0.0.4ee855c0NmXwnT
class User {
name: string;
}
// 注册元数据
Reflect.defineMetadata('description', '这是 name 属性', User.prototype, 'name');
// 读取元数据
Reflect.getMetadata('description', User.prototype, 'name');
为什么我们需要元数据?以上面的例子为例,当我们为 Class 的属性注入了元数据后,就可以通过收集这些元数据来自动生成详细的说明文档,而对原本的运行时没有任何影响。更进一步的,我们可以注入描述数据类型的元数据来实现运行时的额外校验逻辑,注入描述额外操作的元数据来实现面向切面编程,注入描述依赖的元数据来实现依赖注入...等等。
而元数据之所以和装饰器有着紧密的关联,最重要的一点就是装饰器能够在满足元数据注册/读取入参的同时,提供更简洁的 API,比如 reflect-metadata 中自带的装饰器 API:
class User {
@Reflect.metadata('description', '这是 name 属性')
name: string;
}
type Decorator = (value: Input, context: {
kind: string;
name: string | symbol;
access: {
get?(): unknown;
set?(value: unknown): void;
};
isPrivate?: boolean;
isStatic?: boolean;
addInitializer?(initializer: () => void): void;
// 新增 metadata 属性
metadata?: Record<string | number | symbol, unknown>;
}) => Output | void;
function meta(key, value) {
return (_, context) => {
context.metadata[key] = value;
};
}
@meta('a' 'x')
class C {
@meta('b', 'y')
m() {}
}
context.metadata
这个对象,即 Class 的元数据信息会被合并到类的 [Symbol.metadata]
属性上:
C[Symbol.metadata].a; // 'x'
C[Symbol.metadata].b; // 'y'
class D extends C {
@meta('b', 'z')
m() {}
}
D[Symbol.metadata].a; // 'x' 继承
D[Symbol.metadata].b; // 'z' 覆盖
在 5.2 版本中,TypeScript 对应地支持了装饰器元数据提案,实现方式当然也是同样的 context.metadata
。如果你希望了解更多关于新版装饰器的使用方式,可以查看下面这段话。
Unable to resolve signature of class decorator when called as an expression, The runtime will invoke the decorator with 3 arguments, but the decorator expects 1
这样的错误,其原因就是你的本地 TS 版本已经是 5.0,TS Server 会使用新版的装饰器语法来进行检查,而你本地还是按照旧版装饰器的语法来的。
匿名 & 具名元祖元素混用
TypeScript 支持描述一个定长、元素类型固定的数组类型,即元组,如:
const arr: [string, string, string, number] = ['lin', 'bu', 'du', 18];
const arr: [name: string, age: number, male?: boolean] = ['linbudu', 18, true];
Labeled Tuple Elements:https://github.com/Microsoft/TypeScript/issues/28259
const arr: [name: string, number, boolean] = ['linbudu', 18, true];

declare let array: string[] | number[];
// 此表达式不可调用。
array.filter((x) => !!x);
// 此表达式不可调用。
array.every((x) => !!x);
// 此表达式不可调用。
array.find((x) => !!x);
string[]
和
number[]
的类型来通过 filter 的类型检查(注意,不是
(string | number)[]
),即 filter 的参数类型又是 string 又是 number,但很显然并不存在这样的类型。而在 5.2 版本中,TypeScript 改进了检查策略,会为这种数组组成的联合类型执行特殊的策略,即使用每个数组的元素类型来构造一个联合类型,然后作为 filter 的参数类型,比如在上面的类型中,filter 的参数类型会是
string | number
,同时返回值类型也会对应地变为
Array<string | number>
。

本文分享自微信公众号 - 大淘宝技术(AlibabaMTT)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。