TypeScript 4.4 重点:

通常我们会使用类型守卫(type guards)来确认它的类型:

  1. function foo(arg: unknown) {
  2. if (typeof arg === "string") {
  3. // We know 'arg' is a string now.
  4. console.log(arg.toUpperCase());
  5. }
  6. }

但如果我们将判断条件移出并定义为argIsString变量?

function foo(arg: unknown) {
    const argIsString = typeof arg === "string";
    if (argIsString) {
        console.log(arg.toUpperCase());
        //              ~~~~~~~~~~~
        // Error! Property 'toUpperCase' does not exist on type 'unknown'.
    }
}

在之前的版本,TypeScript 会丢失一些信息,这样我们想重复使用相同判断时使用不方便。
在4.4的版本中,则不会报错了。如果类型守卫使用 constreadonly 属性,或一个不可改变的参数,TypeScript会合理的缩小其值。并且不仅仅是 typeof 检查,其他类型守卫的值也会保留。

type Shape =
    | { kind: "circle", radius: number }
    | { kind: "square", sideLength: number };

function area(shape: Shape): number {
    const isCircle = shape.kind === "circle";
    if (isCircle) {
        // 这里是 circle
        return Math.PI * shape.radius ** 2;
    }
    else {
        // 剩下的是 square 
        return shape.sideLength ** 2;
    }
}

甚至可以再深入判断,提取了字段也能缩小原来对象的类型判断:

type Shape =
    | { kind: "circle", radius: number }
    | { kind: "square", sideLength: number };

function area(shape: Shape): number {
    // 先提前 'kind'
    const { kind } = shape;

    if (kind === "circle") {
        return Math.PI * shape.radius ** 2;
    }
    else {
        return shape.sideLength ** 2;
    }
}

Symbol and Template String Pattern Index Signatures

支持 symbol类型和模板字符串类型的索引签名

使用索引允许我们定义像词典那样的对象。

// 字符串:key为string,value为boolean
interface BooleanDictionary {
    [key: string]: boolean;
}

declare let myDict: BooleanDictionary;

// Valid to assign boolean values
myDict["foo"] = true;
myDict["bar"] = false;

// Error, "oops" isn't a boolean
myDict["baz"] = "oops";
// 数字
interface Array<T> {
    [index: number]: T;

    // ...
}

let arr = new Array<string>();

// Valid
arr[0] = "hello!";

// Error, expecting a 'string' value here
arr[1] = 123;

但是目前索引被限制为 string 或 number 类型,意味着不能使用symbol,也不能使用一些字符串的子集,比如data-开头的文字:
在4.4的版本中,支持symbol类型和模板字符串类型:

// symbol
interface Colors {
    [sym: symbol]: number;
}

const red = Symbol("red");
const green = Symbol("green");
const blue = Symbol("blue");

let colors: Colors = {};

colors[red] = 255;          // Assignment of a number is allowed
let redVal = colors[red];   // 'redVal' has the type 'number'

colors[blue] = "da ba dee"; // Error: Type 'string' is not assignable to type 'number'.
// template string pattern type
interface Options {
    width?: number;
    height?: number;
}

let a: Options = {
    width: 100,
    height: 100,
    "data-blah": true, // Error! 'data-blah' wasn't declared in 'Options'.
};

interface OptionsWithDataProps extends Options {
    // Permit any property starting with 'data-'.
    [optName: `data-${string}`]: unknown;
}

let b: OptionsWithDataProps = {
    width: 100,
    height: 100,
    "data-blah": true,       // Works!

    "unknown-property": true,  // Error! 'unknown-property' wasn't declared in 'OptionsWithDataProps'.
};

最后,索引类型也支持联合类型,只要类型是由这些类型组成的:

  • string
  • number
  • symbol
  • template string patterns (e.g. hello-${string}) ```typescript interface Data {
[optName: string | symbol]: any;

}

// 等同于

interface Data {

[optName: string]: any;
[optName: symbol]: any;

}

<a name="CqVsM"></a>
## Defaulting to the unknown Type in Catch Variables (--useUnknownInCatchVariables)
tsconfig<br />在过去,throw...catch 语句中,catch 返回的err被声明为any:
```typescript
try {
    // Who knows what this might throw...
    executeSomeThirdPartyCode();
}
catch (err) { // err: any
    console.error(err.message); // Allowed, because 'any'
    err.thisWillProbablyFail(); // Allowed, because 'any' :(
}

TypeScript 4.4 添加 --useUnknownInCatchVariables,开启该配置使默认的any类型变为unknown类型:

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);
    }
}

该配置在 --strict 开启后,自动打开。
除非你在catch里明确声明any类型:

try {
    executeSomeThirdPartyCode();
}
catch (err: any) {
    console.error(err.message); // Works again!
}

Exact Optional Property Types (—exactOptionalPropertyTypes)

tsconfig
一些可选属性能被明确定义为 undefined:

interface Person {
    name: string,
    age?: number;
}

// 等价于
interface Person {
    name: string,
    age?: number | undefined;
}

// 意味着age可以被定义为undefined
const p: Person = {
    name: "Daniel",
    age: undefined, // This is okay by default.
};

TypeScript 4.4 添加 --exactOptionalPropertyTypes,约定可选属性应该完全按照所写的解释,意味着 | undefined 没有被添加到类型中:

// With 'exactOptionalPropertyTypes' on:
const p: Person = {
    name: "Daniel",
    age: undefined, // Error! undefined isn't a number
};

该配置不在--strict family中,同时要求--strictNullChecks 配置打开。

static Blocks in Classes

支持 static blocks in classes

class Foo {
    static #count = 0;

    get count() {
        return Foo.#count;
    }

    static {
        try {
            const lastInstances = loadLastInstances();
            Foo.#count += lastInstances.length;
        }
        catch {}
    }
}

tsc —help Updates and Improvements

tsc —help 更新与优化

更新编译器选项的描述,用颜色和其他视觉分离重新设置 —help 菜单的样式:
image.png

Inlay Hints

inlay hints 描述更多的信息:
image.png


参考: