What's new in TypeScript4.4 🤪

1,417 阅读3分钟

前言

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.assignObject.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。

更多信息

Announcing TypeScript 4.4 RC