Quick preview:

  • Separate Write Types on Properties - 属性的读与写属性可以分开写:不仅类支持,对象字面量也支持,接口/对象类型 也添加了此语法。唯一的限制是,getter类型必须是可分配给setter的。
  • override and the --noImplicitOverride Flag - override 关键字与--noImplicitOverride 标识:override 显式告诉typescript子类重写父类方法,打开--noImplicitOverride 标识,对于重写的方法但没写override 关键字 会报错。
  • Template String Type Improvements - 模板字符串优化:1、模板字符串类型推断 2、更好地关联和推断不同模板字符串类型
  • ECMAScript #private Class Elements - 类元素的私有名称:除了属性之外,方法和访问器也可以被赋予私有名称,此外,静态成员现在也可以有私有名称。
  • Always-Truthy Promise Checks - Promise 返回 true 检查:由于Promise方法总是返回true,在strictNullChecks模式下,判断一个Promise方法执行为true 的条件,会引起error。
  • static Index Signatures - 静态签名索引:静态前面索引,不仅可以在实例部分使用,还可以在静态部分使用了。
  • Import Statement Completions - 导入语句自动完成:当编写一个没有路径的import语句时,会有一个可能的导入列表,当您提交一个导入时,将完成完整的import语句.。
  • Editor Support for @link Tags - 编辑器支持理解@link标签:鼠标悬停在@link标签中的名称上,可以快速预览、跳转相关信息。

Separate Write Types on Properties

在JavaScript中,在存储之前转换传入的值是很常见的。这种情况也经常发生在getter和setter中。例如,假设我们有一个类,它的setter总是在将值保存到私有字段之前将其转换为数字。

  1. class Thing {
  2. #size = 0;
  3. get size() {
  4. return this.#size;
  5. }
  6. set size(value) {
  7. let num = Number(value);
  8. // Don't allow NaN and stuff.
  9. if (!Number.isFinite(num)) {
  10. this.#size = 0;
  11. return;
  12. }
  13. this.#size = num;
  14. }
  15. }

如何在TypeScript中输入这些JavaScript代码? TypeScript 4.3允许你指定读与写属性的类型。

  1. class Thing {
  2. #size = 0;
  3. get size(): number {
  4. return this.#size;
  5. }
  6. set size(value: string | number | boolean) {
  7. let num = Number(value);
  8. // Don't allow NaN and stuff.
  9. if (!Number.isFinite(num)) {
  10. this.#size = 0;
  11. return;
  12. }
  13. this.#size = num;
  14. }
  15. }

当考虑到两个同名的属性如何相互关联时,TypeScript只会使用“reading”类型(例如get访问器中的类型),“写入”类型只在直接写入属性时被考虑。

  1. let thing = new Thing();
  2. // Assigning other types to `thing.size` works!
  3. thing.size = "hello";
  4. thing.size = true;
  5. thing.size = 42;
  6. // Reading `thing.size` always produces a number!
  7. let mySize: number = thing.size;

请记住,这不是一种仅限于的模式。您可以在对象字面量中编写具有不同类型的getter和setter。

  1. function makeThing(): Thing {
  2. let size = 0;
  3. return {
  4. get size(): number {
  5. return size;
  6. },
  7. set size(value: string | number | boolean) {
  8. let num = Number(value);
  9. // Don't allow NaN and stuff.
  10. if (!Number.isFinite(num)) {
  11. size = 0;
  12. return;
  13. }
  14. size = num;
  15. }
  16. }
  17. }

实际上,我们也已经为接口/对象类型添加了语法,以支持属性上的不同读写类型。

  1. interface Thing {
  2. get size(): number
  3. set size(value: number | string | boolean);
  4. }

使用不同类型读属性和写属性的一个限制是,读取属性的类型必须是可分配给正在写入的类型的。换句话说,getter类型必须是可分配给setter的。

override and the --noImplicitOverride Flag

class tsconfig
在JavaScript中扩展类时,该语言非常容易覆盖方法——但不幸的是,您可能会遇到一些错误。

其中之一就是忘记重命名。例如下面的例子:

  1. class SomeComponent {
  2. show() {
  3. // ...
  4. }
  5. hide() {
  6. // ...
  7. }
  8. }
  9. class SpecializedComponent extends SomeComponent {
  10. show() {
  11. // ...
  12. }
  13. hide() {
  14. // ...
  15. }
  16. }

SpecializedComponent继承子类SomeComponent,并覆盖了show和hide方法。如果有人决定去掉show和hide,改成用一个方法替换它们,会发生什么?

 class SomeComponent {
-    show() {
-        // ...
-    }
-    hide() {
-        // ...
-    }
+    setVisible(value: boolean) {
+        // ...
+    }
 }
 class SpecializedComponent extends SomeComponent {
     show() {
         // ...
     }
     hide() {
         // ...
     }
 }

SpecializedComponent没有更新。现在它只是添加了这两个无用的show和hide方法,这两个方法可能不会被调用。
问题的原因在于,我们无法确认我们想要新增一个方法,还是覆盖已存在的方法。所以TypeScript 4.3 添加了 override 关键字。

class SpecializedComponent extends SomeComponent {
    override show() {
        // ...
    }
    override hide() {
        // ...
    }
}

当方法添加了 override, TypeScript 会确认在 base class 是否有相同名字的方法存在。

这是一个很大的改进,但如果您忘记添加override,这并没有帮助。
所以TypeScript 4.3还提供了一个新的 --noImplicitOverride 标志。当此选项被启用时,除非显式地使用override关键字,否则重写父类中的任何方法将引起错误。

Template String Type Improvements

模板字符串类型(template string type)可以使我们通过拼接构建新的类型

type Color = "red" | "blue";
type Quantity = "one" | "two";

type SeussFish = `${Quantity | Color} fish`;
// same as
//   type SeussFish = "one fish" | "two fish"
//                  | "red fish" | "blue fish";

或者与其他类字符串的类型(string-like types)匹配

declare let s1: `${number}-${number}-${number}`;
declare let s2: `1-2-3`;

// Works!
s1 = s2;

优化1:模板字符串类型推断

当模板字符串在上下文中被定义为字符串表达式类型(string-literal-like type),它会尝试给这个表达式一个模板类型(template type)。

function bar(s: string): `hello ${string}` {
    // 之前会报错:Type 'string' is not assignable to type '`hello ${string}`'.
      // 现在是可行的
    return `hello ${s}`;
}

在推断类型中,类型参数继承字符串(extends string)时,也会出现这种情况:

declare let s: string;
declare function f<T extends string>(x: T): T;

// 之前推断类型为: string
// 现在推断类型为: `hello-${string}`
let x2 = f(`hello ${s}`);

优化2:更好地关联和推断不同模板字符串类型

declare let s1: `${number}-${number}-${number}`;
declare let s2: `1-2-3`;
declare let s3: `${number}-2-3`;

s1 = s2;
// Type '`${number}-2-3`' is not assignable to type '`${number}-${number}-${number}`'.
s1 = s3;

当检查像s2那样的字符串字面量类型时,TypeScript可以匹配字符串的内容,并发现在第一次赋值时s2与s1是兼容的;然而,当它看到另一个模板字符串时,它就放弃了。因此,像s3到s1这样的赋值操作就不起作用了。
实际上,TypeScript现在要做的是证明模板字符串的每一部分是否能够成功匹配。你现在可以用不同的替换来混合和匹配模板字符串,TypeScript会很好地判断它们是否真的兼容。

declare let s1: `${number}-${number}-${number}`;
declare let s2: `1-2-3`;
declare let s3: `${number}-2-3`;
declare let s4: `1-${number}-3`;
declare let s5: `1-2-${number}`;
declare let s6: `${number}-2-${number}`;

// Now *all of these* work!
s1 = s2;
s1 = s3;
s1 = s4;
s1 = s5;
s1 = s6;

在进行这项工作时,我们肯定还会添加更好的推理能力

declare function foo<V extends string>(arg: `*${V}*`): V;

function test<T extends string>(s: string, n: number, b: boolean, t: T) {
    let x1 = foo("*hello*");            // "hello"
    let x2 = foo("**hello**");          // "*hello*"
    let x3 = foo(`*${s}*` as const);    // string
    let x4 = foo(`*${n}*` as const);    // `${number}`
    let x5 = foo(`*${b}*` as const);    // "true" | "false"
    let x6 = foo(`*${t}*` as const);    // `${T}`
    let x7 = foo(`**${s}**` as const);  // `*${string}*`
}

ECMAScript #private Class Elements

TypeScript 4.3扩展了类中可以被赋予#private #name 的元素,使它们在运行时真正私有。除了属性之外,方法访问器也可以被赋予私有名称。

class Foo {
    #someMethod() {
        //...
    }

    get #someValue() {
        return 100;
    }

    publicMethod() {
        // These work.
        // We can access private-named members inside this class.
        this.#someMethod();
        return this.#someValue;
    }
}

new Foo().#someMethod();
//        ~~~~~~~~~~~
// error!
// Property '#someMethod' is not accessible
// outside class 'Foo' because it has a private identifier.

new Foo().#someValue;
//        ~~~~~~~~~~
// error!
// Property '#someValue' is not accessible
// outside class 'Foo' because it has a private identifier.

静态成员现在也可以有私有名称:

class Foo {
    static #someMethod() {
        // ...
    }
}

Foo.#someMethod();
//  ~~~~~~~~~~~
// error!
// Property '#someMethod' is not accessible
// outside class 'Foo' because it has a private identifier.

Always-Truthy Promise Checks

tsconfig
strictNullChecks模式下,判断一个Promise方法执行为true 的条件,会引起error:

async function foo(): Promise<boolean> {
    return false;
}

async function bar(): Promise<string> {
    if (foo()) {
    //  ~~~~~
    // Error!
    // This condition will always return true since
    // this 'Promise<boolean>' appears to always be defined.
    // Did you forget to use 'await'?
        return "true";
    }
    return "false";
}

static Index Signatures

class
索引签名允许我们在值上设置比显式声明的类型更多的属性。

class Foo {
    hello = "hello";
    world = 1234;

    // This is an index signature:
    [propName: string]: string | number | undefined;
}

let instance = new Foo();

// Valid assigment
instance["whatever"] = 42;

// Has type 'string | number | undefined'.
let x = instance["something"];

之前,索引签名只能在类的实例部分声明,现在,索引签名现在可以声明为静态。

class Foo {
    static hello = "hello";
    static world = 1234;

    static [propName: string]: string | number | undefined;
}

// Valid.
Foo["whatever"] = 42;

// Has type 'string | number | undefined'
let x = Foo["something"];

在类的静态部分应用于索引签名的规则与在实例部分应用于索引签名的规则是相同的——也就是说,每个其他静态属性都必须与索引签名兼容。

class Foo {
    static prop = true;
    //     ~~~~
    // Error! Property 'prop' of type 'boolean'
    // is not assignable to string index type
    // 'string | number | undefined'.

    static [propName: string]: string | number | undefined;
}

Import Statement Completions

在使用JavaScript的import和export语句时,用户遇到的最大问题之一是顺序——
导入是这样写

import { func } from "./module.js";

而不是

from "./module.js" import { func };

这在使用全部导入的时候,会造成一些痛苦,因为此时 auto-complete 无法正确导入。例如,如果你开始写 import{ 之类的东西,TypeScript并不知道你打算从哪个模块中导入,所以它不能提供任何限定范围的补全。
为了缓解这一问题,Auto-imports 解决了这个问题,它提供每一个可能的导出,并在文件的顶部自动插入一个import语句。
因此,当您现在开始编写一个没有路径的import语句时,我们将提供一个可能的导入列表。当您提交一个导入时,我们将完成完整的import语句,包括您将要编写的路径。

image.png
这个要求编辑器支持这个特性,你可以使用最新的 VSCode 来尝试。

Editor Support for @link Tags

TypeScript现在可以理解@link标签,并尝试解析它们所链接到的声明。这意味着您可以将鼠标悬停在@link标签中的名称上,获得快速信息,或者使用go-to-definition或find-all-references等命令。

例如,你可以在下面的例子中在@link bar中转到bar的定义,typescript支持的编辑器就会跳转到bar的函数声明。

/**
 * This function depends on {@link bar}
 */
function foo() {

}

function bar() {

}

参考: