Typescript 4.2新特性一览

1,371 阅读2分钟

更智能的保留类型别名

Typescript可以为类型声明新的名称,即类型别名

type BasicPrimitive = number | string | boolean

type BasicPrimitiveOrSymbal = BasicPrimitive | symbol

Visual Studio CodeTypeScript Playground 之类的编辑器上,将鼠标悬停于想查看的类型之上时,会展示一个信息面板,显示其类型。鼠标悬停查看BasicPrimitiveOrSymbal

在4.2之前的版本中

在4.2中

可以看出在4.2版本中,对类型别名进行了保留

信息面板中展示的类型就是TS对此类型的推断,故此规则也适用于编译出的类型声明文件(.d.ts输出)

export type BasicPrimitive = number | string | boolean;
export function doStuff(value: BasicPrimitive) {
    if (Math.random() < 0.5) {
        return Symbal();
    }
    return value;
}

如上函数doStuff的返回值在4.2以下版本将被推断为number | string | boolean | symbol,在4.2中将会被推断为BasicPrimitive | symbol

编译输出的*.d.ts文件,在4.2之前的版本中

export declare type BasicPrimitive = number | string | boolean;
export declare function doStuff(value: BasicPrimitive): string | number | boolean | symbol;

在4.2版本中

export declare type BasicPrimitive = number | string | boolean;
export declare function doStuff(value: BasicPrimitive): symbol | BasicPrimitive;

Rest参数可用于元祖的任何位置

type T1 = [...string[], number] // 任意个string跟着一个number

type T2 = [number, ...string[], number] // string之前各有一个number

在之前版本中Rest参数只能在元祖末尾处使用

type T1 = [number, ...string[]] // number后跟着任意个string

type T2 = [boolean, ...string[], number] // ❌ error: A rest element must be last in a tuple type

在4.0版本中可以通过定义类型别名的方式规避报错,但类型会被推断为Rest参数的类型和其后所有类型的联合类型

type t1 = string[]

type t2 = [boolean, ...t1, number] // 被推断为[boolean, ...(string | number)[]]

虽在4.2版本中Rest参数可用于元祖的任意位置,但仍有一些限制

  • 元祖中只能有一个Rest参数
  • Rest参数后不能出现可选参数
type T1 = [...string[], ...number[]] // ❌ error: A rest element cannot follow another rest element.

type T2 = [...string[], number?] // ❌ error: An optional element cannot follow a rest element.

对第一条规则4.2.2版本仍可以通过定义类型别名的方式规避报错

type T1 = number[]

type T2 = [...string[], ...T] // 被推断为 (string | number)[]

--noPropertyAccessFromIndexSignature标志

开启标志后索引签名中未显式声明的属性通过.操作符访问将会抛出一个错误。具体是否使用看个人习惯

interface Option {
    name: string;
    [x: string]: any;
}

declare const a: Option

let b = a['age']

let c = a.age // ❌ error:  Property 'age' comes from an index signature, so it must be accessed with ['age']

abstract构造符

再类上使用abstract声明,表明类只能被继承,而不能被通过new操作符调用

而这带来的一个问题是,抽象类与new (...args: any) => any不兼容

abstract class Shape {
    abstract name: string;
    abstract getArea(): number;
}

new Shape() // error: Cannot create an instance of an abstract class.

const a: new (...args: any) => any = Shape // ❌ error: Type 'typeof Shape' is not assignable to type 'new (...args: any) => any'.

// InstanceType内置类型也用了new (...args: any) => any实现的
type MyInstance = InstanceType<typeof Shape> // ❌ error: Type 'typeof Shape' is not assignable to type 'new (...args: any) => any'.

而在TS 4.2中abstract可作为构造符使用,由此就可以定义一个获取抽象类示例类型的别名

type AbstractInstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any

type MyInstance = AbstractInstanceType<typeof Shape>; // Shape

--explainFiles标志

使用此标志去编译,TS将给出非常详细的关于import信息的说明

tsc --explainFiles

打印例子如下:

node_modules/typescript/lib/lib.d.ts
  Default library
node_modules/typescript/lib/lib.es5.d.ts
  Library referenced via 'es5' from file 'node_modules/typescript/lib/lib.es2015.d.ts'
  Library referenced via 'es5' from file 'node_modules/typescript/lib/lib.d.ts'
node_modules/typescript/lib/lib.es2015.d.ts
  Library referenced via 'es2015' from file 'node_modules/typescript/lib/lib.es2016.d.ts'
node_modules/typescript/lib/lib.es2016.d.ts
  Library referenced via 'es2016' from file 'node_modules/typescript/lib/lib.es2017.d.ts'
node_modules/typescript/lib/lib.es2017.d.ts
...

此外,还可以指定信息的输出位置

# 将信息输出到.text文件
tsc --explainFiles > expanation.txt

# 将信息输出到 VS Code

tsc --explainFiles | code -

改进逻辑表达式中的未调用函数检查

在使用--strictNullChecks时,&&||将进行未调用函数检查

function foo() { return false }

function is_foo_1() { return foo ? 1 : 0 } // ❌ error: This condition will always return true since the function is always defined. Did you mean to call it instead

function is_foo_2() { return foo && 1 } // ❌ error: This condition will always return true since the function is always defined. Did you mean to call it instead

function is_foo_3() { return foo || 1 } // ❌ error: This condition will always return true since the function is always defined. Did you mean to call it instead

解构变量可以显式标记为未使用

在解构的变量名上通过前缀增加下划线_的方式显式声明该变量不使用

let [_first, second] = getValues();

参考资料

Announcing TypeScript 4.2