Typescript 4.9重点特性探索
toc
这里是 TypeScript 4.9 更新的部分内容
- satifies 操作符
- in操作符中未列举的属性收束
- Class 的 Auto-Accessor
- 对于 NaN 进行检查
- 编辑器增强:“Remove Unused Imports” 和 “Sort Imports”
- 编辑器增强:对于 return 关键字的 Go-to-Definition
satisfies 操作符
TypeScript 开发者可能遇到的一个问题:既要确保表达式匹配某些类型,又要保留该表达式的具体类型。
比如我们定义一个颜色对象
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255]
};
因为每个属性都被赋予了默认值, ts
会自动帮我们自动推导 palette
的属性类型,所以我们可以直接调用它们的方法:
const a = palette.red.at(0); // red 被推断为 number[] 类型
const b = palette.green.toUpperCase(); // green 被推断为 string 类型
由于颜色都是固定的,我们想让我们的 palette
对象拥有特定的几个属性,来避免我们写出一些错别字:
const palette = {
red: [255, 0, 0],
green: "#00ff00",
bleu: [0, 0, 255] // 故意写错
};
当我们为 palette
定义一个类型,就可以检测出错别字
type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
const palette: Record<Colors, string | RGB> = {
red: [255, 0, 0],
green: "#00ff00",
bleu: [0, 0, 255] // ~~~~ The typo is now correctly detected
};
这时候我们再调用 palette.blue
的方法,这时ts
的类型推断会出错:
type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
const palette: Record<Colors, string | RGB> = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255]
};
// 'palette.blue' "could" 的类型是 string | RGB ,所以它不一定存在 at 方法
const a = palette.blue.at(0);
如此就暴露出一个问题,我们用更严格的类型约束了写出bug
的可能性,但是却失去了类型推断的能力。
satisfies
关键字就是用来解决这个问题的,它既能让我们验证表达式的类型是否与某个类型匹配,也可以保留基于值进行类型推断的能力。
type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
const palette = {
red: [255, 0, 0],
green: "#00ff00",
bleu: [0, 0, 255] // 可以捕获到错别字bleu
} satisfies Record<Colors, string | RGB>;
// 以下两种方法都可以调用
const a = palette.bleu.at(0);
const b = palette.bleu.toUpperCase();
由此我们可以看到新增 satisfies
操作符,类似于 as
,但他更像一个不那么 strict
的 as
in操作符中未列举的属性收束
我们经常需要处理程序运行时不确定的类型。我们从服务器或者配置文件读一个数据,并不能完全确定这个属性是否存在,JavaScript
的in
操作符提供了检查一个字段是否存在的手段。
在之前,TypeScript
也提供了一定的对使用in
操作符进行类型收束。
interface RGB {
red: number;
green: number;
blue: number;
}
interface HSV {
hue: number;
saturation: number;
value: number;
}
function setColor(color: RGB | HSV) {
if ("hue" in color) {
// 'color'd HSV
}
// ...
}
类型 RGB 并没有 hue 字段,所以可以进行类型收束,在in的block中,类型被收束为 HSV。
但是,如果没有进行类型标准,会变成什么样子呢?
function tryGetPackageName(context) {
const packageJSON = context.packageJSON;
// 检查我们收到的类型是一个 object.
if (packageJSON && typeof packageJSON === "object") {
// 检查存在 name 字段.
if ("name" in packageJSON && typeof packageJSON.name === "string") {
return packageJSON.name;
}
}
return undefined;
}
把上面的例子改写为ts
,并使用 unknown
类型。
interface Context {
packageJSON: unknown;
}
function tryGetPackageName(context: Context) {
const packageJSON = context.packageJSON;
// 检查我们收到的类型是一个 object.
if (packageJSON && typeof packageJSON === "object") {
// 检查存在 name 字段.
if ("name" in packageJSON && typeof packageJSON.name === "string") {
// ~~~~
// error! Property 'name' does not exist on type 'object.
return packageJSON.name;
// ~~~~
// error! Property 'name' does not exist on type 'object.
}
}
return undefined;
}
这里会报错是因为,在ts
4.9之前的版本,虽然unkown
被收束为object
,但是之后的收束并没有生效,TypeScript依然认为 packageJSON 只是一个object
,而不知道有name字段。
TypeScript4.9 优化了这个问题,在通过in
操作符以后,会给类型添加上断言添加的类型 Record<"property-key-being-checked", unknown>
。
所以,在 TypeScript 4.9 中,packageJSON 的类型会先从unknown收束为object,然后继续收束为 object & Record<"name", unknown>
,这样直接访问 packageJSON.name
就不会报错了。
interface Context {
packageJSON: unknown;
}
function tryGetPackageName(context: Context): string | undefined {
const packageJSON = context.packageJSON;
// 检查我们收到的类型是一个 object.
if (packageJSON && typeof packageJSON === "object") {
// 检查存在 name 字段.
if ("name" in packageJSON && typeof packageJSON.name === "string") {
// 不会报错了!
return packageJSON.name;
}
}
return undefined;
}
TypeScript
也会对in操作符两端做检查,确保左边是 string | number | symbol, 右边是object。这会保证我们检查的左边是合法的key
,而右边不是在检查一个基础类型。
这个功能虽然简单,但是让 TypeScript
的断言能力进一步提升,为开发者写出更安全的代码提供了方便。
Auto-Accessors in Classes
TypeScript 4.9 支持 ECMAScript
中即将推出的功能,称为自动访问器,自动访问器的声明就像类的属性一样,只是它们用 accessor
关键字声明
class Person {
accessor name: string;
constructor(name: string) {
this.name = name;
}
}
类的自动访问器会转化为具有无法访问的私有属性的获取和设置访问器。
class Person {
#__name: string;
get name() {
return this.#__name;
}
set name(value: string) {
this.#__name = name;
}
constructor(name: string) {
this.name = name;
}
}
对这个功能关心的话,请查看 pr。
对比较NaN进行检查
对于JavaScript
开发者来说,检查一个值和NaN
的关系是一件不容易的事。因为NaN
是一个特殊的数字型值,表示 “不是一个数字”。任何值和NaN
都不相等,包括NaN
自己。
console.log(NaN == 0) // false
console.log(NaN === 0) // false
console.log(NaN == NaN) // false
console.log(NaN === NaN) // false
和这个等价的另一个规则是,任何东西都和NaN
不相等。
console.log(NaN != 0) // true
console.log(NaN !== 0) // true
console.log(NaN != NaN) // true
console.log(NaN !== NaN) // true
这个奇怪的行为并不是JavaScript
独有的,任何语言只要实现了 IEEE-754 floats
标准,就会有这个行为。但是 JavaScript
的原生数字类型是一个浮点数型数字值,并且 JavaScript
的数字解析经常会出现NaN
。检查和 NaN
在处理数字相关的代码时,是比较常见的。正确的做法是使用Number.isNaN
函数来判断,但是很多开发者选择使用someValue === NaN
来实现这个功能,这样就会引发一些不必要的bug
在TypeScript4.9
会对NaN
的直接比较进行报错,提示开发者使用Number.isNaN
函数。
function validate(someValue: number) {
return someValue !== NaN;
// ~~~~~~~~~~~~~~~~~
// error: This condition will always return 'true'.
// Did you mean '!Number.isNaN(someValue)'?
}
我们认为这个改变能帮助新手开发者防止错误,就像 TypeScript
目前不可以比较 object
和 array
一样。
编辑器增强:“Remove Unused Imports” 和 “Sort Imports”
在之前的版本,TypeScript
只支持两个编辑器命令来管理 import。 例如:
import { Zebra, Moose, HoneyBadger } from "./zoo";
import { foo, bar } from "./helper";
let x: Moose | HoneyBadger = foo();
第一个称为 “组织导入 -Organize Imports”,会把不使用的 imports 移除,然后对剩下的import
进行排序,上面的文件会被重写为:
import { foo } from "./helper";
import { HoneyBadger, Moose } from "./zoo";
let x: Moose | HoneyBadger = foo();
在 TypeScript4.3
,引入了 “Sort Import” 命令,可以只对文件进行排序,而不移除它们,使用这个功能会让一开始的代码变为:
import { bar, foo } from "./helper";
import { HoneyBadger, Moose, Zebra } from "./zoo";
let x: Moose | HoneyBadger = foo();
使用 “Sort Imports” 的缺陷是,在Visual Studio Code中,这个功能只能是保存时调用功能,而不是手动触发的功能。
TypeScript 4.9
增加了另一半功能,“删除未使用的导入 - Remove Unused Imports” 功能命令,TypeScript
可以移除不使用的import
和语句,把剩下的代码留下,但会单独保留其相对顺序
import { Moose, HoneyBadger } from "./zoo";
import { foo } from "./helper";
let x: Moose | HoneyBadger = foo();
这个功能对于全部编辑器可用,但是注意 Visual Studio Code(1.73 和之后)会支持内置的可以在命令面板调用这些功能。用户如果想更细粒度地控制这个行为,可以混合调用 “Remove Unused Imports”、“Sort Imports” 和 “Organize Imports”。
更详细的文档请参考。
编辑器增强:对于 return 关键字的 Go-to-Definition
在编辑器中,当对return
关键字执行go-to-definition,TypeScript
会跳到相关函数的顶部,这有助于我们快速了解 return
属于哪个函数。
我们期望 TypeScript 可以扩展这个行为到更多的关键字,比如 await 和 yield,switch、case 和 default。
相关文章
- Typescript: Getting Started
- 前端 QA 工具链指南 - husky,commitlint,commitizen,eslint,typescript,express
- TypeScript高级类型-Partial
- 「使用 webpack5从0到1搭建React+TypeScript 项目环境」1. React 与 TypeScript 集成
- 智能合约开发——TypeScript 基础(全)
- TypeScript:Uncaught TypeError: Cannot read properties of null 错误
- TypeScript介绍和使用
- Java和TypeScript开发者之间的区别必须知道
- 解释器模式举例-TypeScript 类型体操天花板,用类型运算写一个 Lisp 解释器
- TypeScript笔记
- TypeScript 中 as const 是什么
- 软件开发入门教程网之TypeScript 声明文件
- 软件开发入门教程网之TypeScript 基础语法
- Laravel + Vue 3(Vite、TypeScript)SPA 设置
- (一)为什么要使用 TypeScript(二)安装并配置 Ts
- 开心档之TypeScript 变量声明