[译]TypeScript 3.8 更新

1,843 阅读2分钟

原文链接 Announcing TypeScript 3.8 by Daniel,本文参考翻译了部分原文

TypeScript近期更新了3.8版本,带来了许多新特性

1. 类型导入和导出(Type-Only Imports and Exports)

这个特性可能平时大家基本用不到,但是如果碰到过类似问题的话大家可能对这个会感兴趣一些(尤其是在--iosolateModules下变编译,或者使用transplieModule API, 或者Babel)

问题出现的原因是Typescript在引用类型的时候复用了Javascript的import语法。

// ./foo.ts
interface Options {
    // ...
}

export function doThing(options: Options) {
    // ...
}

// ./bar.ts
import { doThing, Options } from "./foo.js";

function doThingBetter(options: Options) {
    // do something twice as good
    doThing(options);
    doThing(options);
}

这样导入doThingOptions很方便,因为大多数情况下我们不关心导入的具体内容,只知道导入了一些东西。不幸的是代码可以正常运行是因为一个被称为导入省略(import elision)的特性,它发现Options是一个类型,然后自动去掉了它的导入。真正的执行结果是:

// ./foo.js
export function doThing(options: Options) {
    // ...
}

// ./bar.js
import { doThing } from "./foo.js";

function doThingBetter(options: Options) {
    // do something twice as good
    doThing(options);
    doThing(options);
}

也许这也不能引起你的注意,但是当代码是以下这种情况的时候会出现混乱

import { MyThing } from "./some-module.js";

export { MyThing };

我们根本无法确定MyThing是否是一个类型

为了避免出现类似情况,TypeScript3.8 添加了一个新的类型导入和导出的语法,如下:

import type { SomeThing } from "./some-module.js";

export type { SomeThing };

需要注意的是import type 只是为了注释和申明,它会被删除,在运行时无任何意义

2. ECMAScript 私有变量(ECMAScript Private Fields)

Typescript3.8 提供了ECMAScript私有属性(ECMAScript’s private fields),这个特性是stage-3 class fields proposal的一部分。

class Person {
    #name: string

    constructor(name: string) {
       this.#name = name;
    }

    greet() {
        console.log(`Hello, my name is ${this.#name}!`);
    }
}

let jeremy = new Person("Jeremy Bearimy");

jeremy.#name
//     ~~~~~
// 属性 '#name' 不能在类Person 外访问
// 因为它有一个私有属性的标识符

ECMAScript私有属性和普通的私有属性的异同:

  • 私有属性以字符 # 开头
  • 每一个私有属性名在包含它的类里面有唯一作用域
  • Typescript 访问修饰符,例如publicprivate 不能用于私有属性上
  • 私有属性不能在它所属类的外边访问或者检测到-即使是JS代码本人,我们称之为强隐私(hard privacy)

对于第二条举例,对于普通的属性在子类种重复申明,父类的中的属性会被覆盖。

class C {
    foo = 10;

    cHelper() {
        return this.foo;
    }
}

class D extends C {
    foo = 20;

    dHelper() {
        return this.foo;
    }
}

let instance = new D();
// 'this.foo' 在每个实例中都引入了相同的属性
console.log(instance.cHelper()); // prints '20'
console.log(instance.dHelper()); // prints '20'

使用私有属性后:

class C {
    #foo = 10;

    cHelper() {
        return this.#foo;
    }
}

class D extends C {
    #foo = 20;

    dHelper() {
        return this.#foo;
    }
}

let instance = new D();
// 'this.#foo' 对于每个类都引入了不同的属性
console.log(instance.cHelper()); // prints '10'
console.log(instance.dHelper()); // prints '20'

private关键字 VS ECMAScript 私有属性

  1. private在运行时会被全部删除,并且private只在编译时有效,而私有属性在类外完全无法访问
  2. 私有属性不用担心类继承中的属性名冲突问题
  3. private关键字修饰的属性和其他属性没什么不同,在运行时都可以快速访问,但是私有属性依赖于WeakMaps 会降低属性访问速度

3. 顶层await(Top-Level await)

以前的await 关键字只能出现在async函数内部:

async function main() {
    const response = await fetch("...");
    const greeting = await response.text();
    console.log(greeting);
}

main()
    .catch(e => console.error(e))

当我们希望一开始就引入的时候往往使用立即执行函数去处理

await Promise.resolve(console.log('🎉'));
// → SyntaxError: await is only valid in async function

(async function() {
  await Promise.resolve(console.log('🎉'));
  // → 🎉
}());

为了避免引入async函数,提供了一个称为顶层 await的特性

const response = await fetch("...");
const greeting = await response.text();
console.log(greeting);

// Make sure we're a module
export {};

通过顶层await我们可以有以下几种用法:

const strings = await import(`/i18n/${navigator.language}`); // 动态引入路径
const connection = await dbConnector(); // 资源初始化
let jQuery;  // 依赖回退
try {
  jQuery = await import('https://cdn-a.example.com/jQuery');
} catch {
  jQuery = await import('https://cdn-b.example.com/jQuery');
}

4. 其他特性

  • export * as ns Syntax
  • JSDoc Property Modifiers
  • Better Directory Watching on Linux and watchOptions
  • Fast and Loose” Incremental Checking
  • Editor Features(onvert to Template String)
  • Editor Features(Call Hierarchy)
  • Breaking Changes

参考文章:

  1. devblogs.microsoft.com/typescript/…
  2. v8.dev/features/to…