前言
TypeScript每次小版本更新都会新增不少特性,也会有一些breaking changes。TypeScript4.1到现在更新围绕模版字符串类型以及type narrowing相关比较多。 列举了一些新的feature,除了列举的这几个之外都是一些性能提升
❤️混叠条件和判别式的控制流分析
看不懂没有关系,直接看例子 Before typescript 4.4
function foo(arg: unknown) {
if (typeof arg === 'string') {
// We know this is a string now.
console.log(arg.toUpperCase())
}
}
function foo(arg: unknown) {
const argIsString = typeof arg === 'string'
if (argIsString) {
console.log(arg.toUpperCase())
// ~~~~~~~~~~~
// Error! Property 'toUpperCase' does not exist on type 'unknown'.
}
}
In TypeScript 4.4
function foo(arg: unknown) {
const argIsString = typeof arg === 'string'
if (argIsString) {
console.log(arg.toUpperCase())
// works just fine
}
}
不仅仅是 typeof 检查,保留了不同类型的类型保护条件。接着往下看。
type Shape = { kind: 'circle'; radius: number } | { kind: 'square'; sideLength: number }
function area(shape: Shape): number {
const isCircle = shape.kind === 'circle'
if (isCircle) {
// We know we have a circle here!
return Math.PI * shape.radius ** 2
}
// We know we're left with a square here!
return shape.sideLength ** 2
}
what!!! 逐渐变态 我们现在甚至可以提前解构出对象中属性,之后判别,而TypeScript可以缩小原始对象的范围。
type Shape = { kind: 'circle'; radius: number } | { kind: 'square'; sideLength: number }
function area(shape: Shape): number {
// Extract out the 'kind' field first.
const { kind } = shape
if (kind === 'circle') {
// We know we have a circle here!
return Math.PI * shape.radius ** 2
}
// We know we're left with a square here!
return shape.sideLength ** 2
}
显然,typescript这次静态检查不仅仅是在特定的几个场景中做了优化,而是很多不同层面上做了提升。
function f(x: string | number | boolean) {
const isString = typeof x === 'string'
const isNumber = typeof x === 'number'
const isStringOrNumber = isString || isNumber
if (isStringOrNumber) {
x // Type of 'x' is 'string | number'.
} else {
x // Type of 'x' is 'boolean'.
}
}
这里的一个巧妙特点是这种分析是可传递的。 如果我们将一个常量分配给其中包含更多常量的条件,并且这些常量每个都被分配了类型保护,那么TypeScript可以稍后传播这些条件。
符号和模板字符串模式索引签名
Before typescript 4.4
interface Foo {
name: string
[index: number]: unknown
// ...
}
interface Bar {
[index: string]: unknown
// ...
}
// 只支持 string 和 number
In TypeScript 4.4 现在支持索引签名类型有
-
string
-
number
-
symbol
-
template string patterns (e.g.
hello-${string}
)
interface Foo {
[index: number]: number;
[k: `hello-${string}`]: unknown;
// ...
}
const a: Foo = {
32: 233,
'hello-name': 'xxx'
// correct
helloname: 0,
// error!
}
// 目前看来这个特性没有太大实用性
在 Catch 中的变量默认为 unknown
这个特性还是非常实用的,我之前在 TypeScript 你学废了吗 中提到过typescript4.0开始可以给catch 中变量显式声明类型,通常声明为unknown是最好的做法。现在typescript4.4默认设置为了unkown。
try {
executeSomeThirdPartyCode()
} catch (err) {
// err: unknown
// Error! Property 'message' does not exist on type 'unknown'.
console.error(err.message)
// Works! We can narrow 'err' from 'unknown' to 'Error'.
if (err instanceof Error) {
console.error(err.message)
}
}
为了开启这个特性,需要打开typescript的strict模式
确切的可选属性类型
Before typescript 4.4
interface Person {
name: string
age?: number
}
// 等同于
interface Person {
name: string
age?: number | undefined
}
const p: Person = {
name: 'Daniel',
age: undefined, // This is okay by default.
}
默认情况下,TypeScript不区分值为 undefined 的存在属性和缺失属性。 虽然这在大多数情况下都有效,但并非所有 JavaScript 代码都做出相同的假设。 Object.assign
、 Object.keys
、对象展开 ({ ...obj })
和 for-in 循环等函数和运算符的行为取决于对象上是否实际存在属性。
In TypeScript 4.4
在TypeScript4.4 中,新标志 --exactOptionalPropertyTypes 指定可选属性类型应完全按照书面解释,这意味着 | undefined 不会添加到类型中:
// With 'exactOptionalPropertyTypes' on:
const p: Person = {
name: 'Daniel',
age: undefined, // Error! undefined isn't a number
}
这个不在strict模式管辖范围内,需要手动打开才生效。
Class中的 static
代码块
首先为什么会有这玩意儿 看这里 How it works
class Foo {
static Foo.count = 0;
// This is a static block:
static {
if (someCondition()) {
Foo.count++;
}
}
}
这些静态块允许你编写具有自己范围的语句序列,这些语句可以访问包含类中的私有字段。 执行时机是一旦类被声明了,会立即执行。
class Foo {
static #count = 0;
get count() {
return Foo.#count;
}
static {
try {
const lastInstances = loadLastInstances();
Foo.#count += lastInstances.length;
}
catch {}
}
}
可以存在多个static代码块 没有静态块,编写上面的代码是可能的,但通常涉及几种不同类型的hack。