本文介绍: TypeScript版本更新记录以下最近每个版本都举例3-4个常用特性,可能是重大更新,也可能是优化或者bug修复等。更详细的请直接查看官网。TypeScript 4.7 现在支持moduleSuffixes自定义模块说明查找方式选项。鉴于上述配置如下所示导入尝试查看相关文件./foo.ios.ts,./foo.native.ts最后./foo.ts

(在人生的道路上,当你的期望一个个落空的时候,你也要坚定,要沉着。——朗费罗)

在这里插入图片描述

TypeScript

官网
在线运行TypeScript代码
第三方中文博客

特性

  1. typescriptjavascript的超集,向javascript继承额外编辑器,在开发阶段就能发现更多的错误
  2. typescript可以javascript运行的任何地方运行,因为它最终依然会编译javascript
  3. typescript能够更好的和编辑器继承,给出只能IDE提示提高开发效率

TypeScript最近各大版本的主要特性总结

TypeScript版本更新记录
以下最近每个版本都举例3-4个常用特性,可能是重大更新,也可能是优化或者bug修复等。更详细的请直接查看官网

5.0

2023 年 3 月 16 日
1. 装饰器(注解)由原来的实验功能改为正式支持

原生写法

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

    greet() {
        console.log("LOG: Entering method.");

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

        console.log("LOG: Exiting method.")
    }
}

使用typescript5.0装饰器的写法

function loggedMethod(originalMethod: any, _context: any) {

    function replacementMethod(this: any, ...args: any[]) {
        console.log("LOG: Entering method.")
        const result = originalMethod.call(this, ...args);
        console.log("LOG: Exiting method.")
        return result;
    }

    return replacementMethod;
}
class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }

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

const p = new Person("Ron");
p.greet();

// Output:
//
//   LOG: Entering method.
//   Hello, my name is Ron.
//   LOG: Exiting method.
2. 新增const类型参数

推断对象类型时,ts 通常会选择一种通用的类型例如,在这种情况下,会将names类型推断string []

type HasNames = { readonly names: string[] };
function getNamesExactly<T extends HasNames>(arg: T): T["names"] {
    return arg.names;
}

// Inferred type: string[]
const names = getNamesExactly({ names: ["Alice", "Bob", "Eve"]});

推断成string []固然没有问题,但由于namesreadonly的,而推断出的类型不是readonly的,这就会产生一些困扰。虽然我们可以通过添加as const解决这个问题,就像这样:

// The type we wanted:
//    readonly ["Alice", "Bob", "Eve"]
// The type we got:
//    string[]
const names1 = getNamesExactly({ names: ["Alice", "Bob", "Eve"]});

// Correctly gets what we wanted:
//    readonly ["Alice", "Bob", "Eve"]
const names2 = getNamesExactly({ names: ["Alice", "Bob", "Eve"]} as const);

这可能很麻烦且容易忘记。在 TypeScript 5.0 中,您现在可以const修饰符添加类型参数声明中,以导致const-like 推理成为默认值

type HasNames = { names: readonly string[] };
function getNamesExactly<const T extends HasNames>(arg: T): T["names"] {
//                       ^^^^^
    return arg.names;
}

// Inferred type: readonly ["Alice", "Bob", "Eve"]
// Note: Didn't need to write 'as const' here
const names = getNamesExactly({ names: ["Alice", "Bob", "Eve"] });
3. 支持多个配置文件extends

我们使用tsconfig.json管理多个项目时,很多情况下都会在一个tsconfig.json配置文件上进行配置扩展比如下面这样:

{
    "extends": "../../../tsconfig.base.json",
    "compilerOptions": {
        "outDir": "../lib",
        // ...
    }
}

但是,在某些情况下,您可能希望从多个配置文件进行扩展。所以为了在此处提供更多灵活性,Typescript 5.0 现在允许继承多个文件

// tsconfig.json
{
    "extends": ["./tsconfig1.json", "./tsconfig2.json"],
    "files": ["./index.ts"]
}
4. 所有枚举变为联合枚举

解决部分场景下的问题

enum Letters {
    A = "a"
}
enum Numbers {
    one = 1,
    two = Letters.A
}

// 5.0 之前,这个语句是不会报错的,因为enum成员都被错误地认为是number类型
// 5.0 之后,这个语句会报类型错误
// Type 'Numbers' is not assignable to type 'number'.ts(2322)
const t: number = Numbers.two; 

详情见pull request

5. moduleResolution bundler

TypeScript 4.7为其和设置引入node16和选项。这些选项的目的是更好模拟 Node.js 中 ECMAScript 模块精确查找规则;然而,这种模式有很多限制,其他工具没有真正强制执行

// entry.mjs
import * as utils from "./utils";     // ❌ wrong - we need to include the file extension.

import * as utils from "./utils.mjs"; // ✅ works

为了模拟打包器的工作方式,TypeScript 现在引入了一种新策略:–moduleResolution bundler

{
    "compilerOptions": {
        "target": "esnext",
        "moduleResolution": "bundler"
    }
}
6. 支持export type

当 TypeScript 3.8 引入纯类型导入时,新语法不允许用于export * from “module“或export * as ns from “module”重新导出。TypeScript 5.0 添加了对这两种形式的支持

// models/vehicles.ts
export class Spaceship {
  // ...
}

// models/index.ts
export type * as vehicles from "./vehicles";

// main.ts
import { vehicles } from "./models";

function takeASpaceship(s: vehicles.Spaceship) {
  // ✅ ok - `vehicles` only used in a type position
}

function makeASpaceship() {
  return new vehicles.Spaceship();
  //         ^^^^^^^^
  // 'vehicles' cannot be used as a value because it was exported using 'export type'.
}
性能优化

速度、内存和包大小优化,以下是相对于4.9的优化

material-ui build time 90%
TypeScript Compiler startup time 89%
Playwright build time 88%
TypeScript Compiler selfbuild time 87%
Outlook Web build time 82%
VS Code build time 80%
typescript npm Package Size 59%

4.9

2022 年 11 月 15 日
1. accessor 关键字支持

accessor 是 ECMAScript 中即将推出的一个关键字,TypeScript 4.9 对它提供了支持

class Person {
    accessor name: string;
 
    constructor(name: string) {
        this.name = name;
    }
}

accessor 关键字可以为该属性运行转换一对 getset 访问私有支持字段访问器。

class Person {
    #__name: string;
 
    get name() {
        return this.#__name;
    }
    set name(value: string) {
        this.#__name = name;
    }
 
    constructor(name: string) {
        this.name = name;
    }
}
2. 优化了in的类型检查

日常开发中,会使用in对对象属性或者方法进行判断,但有时被判断对象类型是未知的,对于未知的类型,ts默认推断unknown。如果使用in,则会推断object,但因为ts不知道具体的对象属性,所以在类型检查时会出错。
比如以下代码中使用in对packageJSON的name字段进行判断时,就出错了

interface Context {
    packageJSON: unknown;
}

function tryGetPackageName(context: Context) {
    const packageJSON = context.packageJSON;
    // Check to see if we have an object.
    if (packageJSON &amp;&amp; typeof packageJSON === "object") {
        // Check to see if it has a string name property.
        if ("name" in packageJSON &amp;&amp; typeof packageJSON.name === "string") {
        //                                              ~~~~
        // error! Property 'name' does not exist on type 'object.
            return packageJSON.name;
        //                     ~~~~
        // error! Property 'name' does not exist on type 'object.
        }
    }

    return undefined;
}

为了解决问题,ts对in的类型检查进行了优化,比如判断name时会自动推断object &amp; Record<“name”, unknown>。

interface Context {
    packageJSON: unknown;
}

function tryGetPackageName(context: Context): string | undefined {
    const packageJSON = context.packageJSON;
    // Check to see if we have an object.
    if (packageJSON && typeof packageJSON === "object") {
        // Check to see if it has a string name property.
        if ("name" in packageJSON && typeof packageJSON.name === "string") {
            // Just works!
            return packageJSON.name;
        }
    }

    return undefined;
}
3. 检查是否相等NaN

在过去开发者通常对NaN进行判断,但这往往会造成业务错误,因为NaN不等于任何值,NaN的类型却和数字相同。
所以在本版本对NaN做等于或者全等于的比较时会直接抛出错误,并建议开发者使用Number.isNaN。

4.8

2022 年 8 月 25 日
1. 优化unknown类型检查

过去对于unknown的推断,ts默认类型依然为unknown,这次优化,对真值的推断默认为object。这样就更有利于开发人员开发

// 示例
function narrowUnknownishUnion(x: {} | null | undefined) {
    if (x) {
        x;  // {}
    }
    else {
        x;  // {} | null | undefined
    }
}
// 过去和现在的对比
function narrowUnknown(x: unknown) {
    if (x) {
        x;  // used to be 'unknown', now '{}'
    }
    else {
        x;  // unknown
    }
}
2. infer改进了模板字符串类型中类型的推断
// SomeNum used to be 'number'; now it's '100'.
type SomeNum = "100" extends `${infer U extends number}` ? U : never;

// SomeBigInt used to be 'bigint'; now it's '100n'.
type SomeBigInt = "100" extends `${infer U extends bigint}` ? U : never;

// SomeBool used to be 'boolean'; now it's 'true'.
type SomeBool = "true" extends `${infer U extends boolean}` ? U : never;
3. 比较数组数组字面量提示错误
if (peopleAtHome === []) {
//  ~~~~~~~~~~~~~~~~~~~
// This condition will always return 'false' since JavaScript compares objects by reference, not value.
    console.log("here's where I lie, broken inside. </3")
    adoptAnimals();
}
4. 改进了绑定模式推理

对于以下代码,ts 会从绑定模式选取一个类型来进行更好的推断。

declare function chooseRandomly<T>(x: T, y: T): T;

let [a, b, c] = chooseRandomly([42, true, "hi!"], [0, false, "bye!"]);
//   ^  ^  ^
//   |  |  |
//   |  |  string
//   |  |
//   |  boolean
//   |
//   number

但对于以下代码,ts依然会出现以下推断。

declare function f<T>(x?: T): T;

let [x, y, z] = f();

这是有问题的,因为没有传入参数,在4.8以前,因为会推断成any,所以没有出错。现在优化了此问题,对于这种推断,ts会报错

4.7

2022 年 5 月 11 日
1. Node.js 中的 ECMAScript 模块支持

TypeScript 4.7 通过两个module设置添加了此功能node16和nodenext

{
    "compilerOptions": {
        "module": "node16",
    }
}

假如有以下代码

// ./foo.ts
export function helper() {
    // ...
}

// ./bar.ts
import { helper } from "./foo"; // only works in CJS

helper();

代码在 CommonJS 模块中有效,但在 ES 模块中会失败,因为相对导入路径需要使用扩展。因此,必须重写它以使用输出的扩展名foo.ts– 因此bar.ts必须从./foo.js.

// ./bar.ts
import { helper } from "./foo.js"; // works in ESM & CJS

helper();

但在4.7中,这些已经支持,不需要再进行手动更改

2. 计算属性控制流分析

比如以下代码

const key = Symbol();

const numberOrString = Math.random() < 0.5 ? 42 : "hello";

const obj = {
    [key]: numberOrString,
};

if (typeof obj[key] === "string") {
    let str = obj[key].toUpperCase();
}

在4.7之前typeof obj[key] === “string”这段代码是不成立的,会触发错误,现在4.7版本对比进行了优化,可以正常工作

3. 改进了对象和方法中的函数推断

比如以下代码可以正常工作

declare function f<T>(arg: {
    produce: (n: string) => T,
    consume: (x: T) => void }
): void;

// Works
f({
    produce: () => "hello",
    consume: x => x.toLowerCase()
});

// Works
f({
    produce: (n: string) => n,
    consume: x => x.toLowerCase(),
});

但这段代码却不可以

// Was an error, now works.
f({
    produce: n => n,
    consume: x => x.toLowerCase(),
});

// Was an error, now works.
f({
    produce: function () { return "hello"; },
    consume: x => x.toLowerCase(),
});

// Was an error, now works.
f({
    produce() { return "hello" },
    consume: x => x.toLowerCase(),
});

虽然定了类型为string,但因为ts没有进行更细粒度的推断而导致错误。
现在4.7对此问题进行了优化,可以正常工作

4. 自定义模块解析策略moduleSuffixes

TypeScript 4.7 现在支持moduleSuffixes自定义模块说明查找方式的选项。

{
    "compilerOptions": {
        "moduleSuffixes": [".ios", ".native", ""]
    }
}

鉴于上述配置如下所示导入

import * as foo from "./foo";

尝试查看相关文件./foo.ios.ts,./foo.native.ts最后./foo.ts。

4.6

2022 年 1 月 21 日
1. 更宽泛的super检查

在 JavaScript 类中,必须 super() 在引用 this. TypeScript 也强制执行这一点,尽管它在确保这一点方面有点过于严格 。 在 TypeScript 中,如果构造函数包含具有任何属性初始值设定项,则在构造函数的开头包含任何代码以前是错误的 。

class Base {
    // ...
}

class Derived extends Base {
    someProperty = true;

    constructor() {
        // error!
        // have to call 'super()' first because it needs to initialize 'someProperty'.
        doSomeStuff();
        super();
    }
}

TypeScript 4.6 现在在该检查方面更加宽松,并允许其他代码在super之前运行

2. 参数控制分析

比如以下代码,当第一个参数字符串“str”时,它的第二个参数是一个字符串,而当第一个自变量字符串“num”时,第二个自变量是一个数字。

function func(...args: ["str", string] | ["num", number]) {
    // ...
}

在ts具有这种剩余参数签名推断的情况下,ts现在可以缩小依赖于其他参数的参数。

type Func = (...args: ["a", number] | ["b", string]) => void;

const f1: Func = (kind, payload) => {
    if (kind === "a") {
        payload.toFixed();  // 'payload' narrowed to 'number'
    }
    if (kind === "b") {
        payload.toUpperCase();  // 'payload' narrowed to 'string'
    }
};

f1("a", 42);
f1("b", "hello");
3. 更准确的索引访问

在4.6之前val.toUpperCase()会报异常,现在4.6优化了这个问题,类型推断更加准确了。

interface TypeMap {
    "number": number;
    "string": string;
    "boolean": boolean;
}

type UnionRecord<P extends keyof TypeMap> = { [K in P]:
    {
        kind: K;
        v: TypeMap[K];
        f: (p: TypeMap[K]) => void;
    }
}[P];

function processRecord<K extends keyof TypeMap>(record: UnionRecord<K>) {
    record.f(record.v);
}

processRecord({
    kind: "string",
    v: "hello!",

    // 'val' used to implicitly have the type 'string | number | boolean',
    // but now is correctly inferred to just 'string'.
    f: val => {
        console.log(val.toUpperCase());
    }
})

4.5

2021 年 11 月 17 日
1. 新的高级类型Awaited并改进Promise

Awaited表示一个 Promise 的 resolve 值类型。
比如以下代码在过去可以使用infer条件类型来进行推断

const pro = Promise.resolve(Promise.resolve(Promise.resolve('hello world')))
type tPro = typeof pro extends Promise<infer U> ? U : typeof pro;

但写起来较为麻烦,现在有了Awaited之后,写法简单明了。

const pro = Promise.resolve(Promise.resolve(Promise.resolve('hello world')))
type tPro = Awaited<typeof pro>

2. 支持node_modules中的lib

为确保 TypeScript 和 JavaScript 支持开箱即用,TypeScript 捆绑了一系列声明文件 ( .d.ts files)。这些声明文件代表 JavaScript 语言中可用的 API 和标准浏览器 DOM API。
不过,使用 TypeScript 包含这些声明文件偶尔会有两个缺点:

  1. 当您升级 TypeScript 时,您还必须处理对 TypeScript 内置声明文件更改,当 DOM API 更改如此频繁时,这可能是一个挑战。
  2. 很难自定义这些文件来使您的需求与项目依赖项的需求匹配例如,如果您的依赖声明它们使用 DOM API,您也可能被迫使用 DOM API)。

ts4.5现在支持指定定义lib包来覆盖默认lib内置方法

{
 "dependencies": {
    "@typescript/lib-dom": "npm:@types/web"
  }
}
3. 更准确的模板字符串类型检查

比如以下代码在过去会报错,现在4.5版本优化了此问题

export interface Success {
    type: `${string}Success`;
    body: string;
}

export interface Error {
    type: `${string}Error`;
    message: string;
}

export function handler(r: Success | Error) {
    if (r.type === "HttpSuccess") {
        // 'r' has type 'Success'
        let token = r.body;
    }
}
4. 支持私有变量检查

ts 4.5现在支持最新的ECMA提案,支持最新的#私有变量类型检查

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

    equals(other: unknown) {
        return other &&
            typeof other === "object" &&
            #name in other && // <- this is new!
            this.#name === other.#name;
    }
}

4.4

2021 年 8 月 12 日
1. 更准确的字符串别名条件类型判断

字符串别名类型检查。比如以下代码,在4.4之前是编译错误的,但在4.4已经可以正常运行

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

判别式类型检查,比如以下代码可以在4.4版本正常运行

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;
    }
    else {
        // We know we're left with a square here!
        return shape.sideLength ** 2;
    }
}
2. 索引类型支持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'.
3. 类内部static静态代码块

其实也是支持最新的ECMA提案,该优化解决了静态属性方法只能在构造函数外部进行初始化问题。有了静态代码块后,就可以直接在类内部初始化静态代码。

class A {
  static x = 3;
  static y;
  static z;

  static {
    // this 为 class 自身
    try {
      const obj = foo(this.x);

      this.y = obj.y;
      this.z = obj.z;
    }
    catch {
      this.y = 0;
      this.z = 0;
    }
  }
}

4.3

2021 年 5 月 12 日
1. 允许指定set写入类型

可以对类和接口的set方法指定参数类型

class Thing {
    #size = 0;

    get size(): number {
        return this.#size;
    }

    set size(value: string | number | boolean) {
        let num = Number(value);

        // Don't allow NaN and stuff.
        if (!Number.isFinite(num)) {
            this.#size = 0;
            return;
        }

        this.#size = num;
    }
}
// Now valid!
interface Thing {
    get size(): number
    set size(value: number | string | boolean);
}
2. 支持方法覆盖(重写)

ts 4.3版本中添加了override标记标记该方法是子类重写父类的方法。

class SomeComponent {
    setVisible(value: boolean) {
        // ...
    }
}
class SpecializedComponent extends SomeComponent {
    override setVisible() {
}
3. 支持对方法和set,get进行私有化命名
class Foo {
    #someMethod() {
        //...
    }
    get #someValue() {
        return 100;
    }
    publicMethod() {
        // 可以使用
        // 可以在类内部访问私有命名成员
        this.#someMethod();
        return this.#someValue;
    }
}
new Foo().#someMethod();
//        ~~~~~~~~~~~
// 错误!
// 属性 '#someMethod' 无法在类 'Foo' 外访问,因为它是私有的。
new Foo().#someValue;
//        ~~~~~~~~~~
// 错误!
// 属性 '#someValue' 无法在类 'Foo' 外访问,因为它是私有的。
4. 更准确的泛型类型检查

在4.3版本之前,以下代码类型检查会出错。在4.3版本中可以正常运行

function makeUnique<T, C extends Set<T> | T[]>(
  collection: C,
  comparer: (x: T, y: T) => number
): C {
  // 假设元素已经是唯一
  if (collection instanceof Set) {
    return collection;
  }
  // 排序然后去重
  collection.sort(comparer);
  //         ~~~~
  // 错误:属性 'sort' 不存在于类型 'C' 上。
  for (let i = 0; i < collection.length; i++) {
    //                           ~~~~~~
    // 错误: 属性 'length' 不存在于类型 'C' 上。
    let j = i;
    while (
      j < collection.length &&
      comparer(collection[i], collection[j + 1]) === 0
    ) {
      //             ~~~~~~
      // 错误: 属性 'length' 不存在于类型 'C' 上。
      //       ~~~~~~~~~~~~~  ~~~~~~~~~~~~~~~~~
      // 错误: 元素具有隐式的 'any' 类型,因为 'number' 类型的表达式不能用来索引 'Set<T> | T[]' 类型。
      j++;
    }
    collection.splice(i + 1, j - i);
    //         ~~~~~~
    // 错误: 属性 'splice' 不存在于类型 'C' 上。
  }
  return collection;
}

4.2

2021 年 2 月 23 日
1. 更智能的类型别名检查

比如代码,我们希望 ts 将 doStuff 的返回值类型显示为 BasicPrimitive | undefined,但是在4.2之前,它却显示成了 string | number | boolean | undefined 类型。

export type BasicPrimitive = number | string | boolean;
export function doStuff(value: BasicPrimitive) {
    if (Math.random() < 0.5) {
        return undefined;
    }
    return value;
}
2. 允许在元组前/中放置剩余元素

在4.2之前,只允许在元组末尾添加剩余参数

let e: [string, string, ...boolean[]];

现在4.2版本,允许在前/中位置添加剩余参数

let foo: [...string[], number];
let bar: [boolean, ...string[], boolean];
3. 更严格的in检查

在4.2之前如果in右边是非对象类属性,则只能在运行时抛出错误。
现在在编译期间就能够发现错误了。

"foo" in 42
//       ~~
// error! The right-hand side of an 'in' expression must not be a primitive.
4. 改进逻辑表达式中的未被调用函数检查

在 –strictNullChecks 模式下,下面的代码会产生错误。

function shouldDisplayElement(element: Element) {
    // ...
    return true;
}
function getVisibleItems(elements: Element[]) {
    return elements.filter((e) => shouldDisplayElement && e.children.length);
    //                          ~~~~~~~~~~~~~~~~~~~~
    // 该条件表达式永远返回 true,因为函数永远是定义了的。
    // 你是否想要调用它?
}

4.1

2020 年 11 月 19 日
1. 支持模板字符串类型
type World = "world";

type Greeting = `hello ${World}`;
// same as
//   type Greeting = "hello world";
2. 允许指定类型映射

在4.1之前,你只能输入指定字符串或自由类型进行映射

type Options = {
    [K in "noImplicitAny" | "strictNullChecks" | "strictFunctionTypes"]?: boolean
};
// same as
//   type Options = {
//       noImplicitAny?: boolean,
//       strictNullChecks?: boolean,
//       strictFunctionTypes?: boolean
//   };
/// 'Partial<T>' is the same as 'T', but with each property marked optional.
type Partial<T> = {
    [K in keyof T]?: T[K]
};

但在4.1版本,我们可以指定类型对象进行映射

type MappedTypeWithNewKeys<T> = {
    [K in keyof T as NewKeyType]: T[K]
    //            ^^^^^^^^^^^^^
    //            This is the new syntax!
}
type Getters<T> = {
    [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};

interface Person {
    name: string;
    age: number;
    location: string;
}

type LazyPerson = Getters<Person>;
// Remove the 'kind' property
type RemoveKindField<T> = {
    [K in keyof T as Exclude<K, "kind">]: T[K]
};

interface Circle {
    kind: "circle";
    radius: number;
}

type KindlessCircle = RemoveKindField<Circle>;
// same as
//   type KindlessCircle = {
//       radius: number;
//   };
3. 递归条件类型

在 JavaScript 中,可以在任意级别展平和构建容器类型的函数相当常见例如考虑.then()的实例上的方法Promise。.then(…)展开每个承诺,直到它找到一个不是“类似承诺”的值,并将该值传递给回调。s 上还有一种相对较新的flat方法Array,可以取多深的深度来展平。
就所有实际意图和目的而言,在 TypeScript 的类型系统中表达这一点是不可能的。虽然有黑客实现这一点,但这些类型最终看起来非常不合理

这就是为什么 TypeScript 4.1 放宽了对条件类型的一些限制——以便它们可以对这些模式进行建模。在 TypeScript 4.1 中,条件类型现在可以在其分支中立即引用自身,从而更容易编写递归类型别名。

例如,如果我们想写一个类型来获取嵌套数组元素类型,我们可以写成下面的deepFlatten类型。

type ElementType<T> =
    T extends ReadonlyArray<infer U> ? ElementType<U> : T;

function deepFlatten<T extends readonly unknown[]>(x: T): ElementType<T>[] {
    throw "not implemented";
}

// All of these return the type 'number[]':
deepFlatten([1, 2, 3]);
deepFlatten([[1], [2, 3]]);
deepFlatten([[1], [[2]], [[[3]]]]);

同样,在 TypeScript 4.1 中,我们可以编写一个Awaited类型来深度解包Promises。

type Awaited<T> = T extends PromiseLike<infer U> ? Awaited<U> : T;

/// Like `promise.then(...)`, but more accurate in types.
declare function customThen<T, U>(
    p: Promise<T>,
    onFulfilled: (value: Awaited<T>) => U
): Promise<Awaited<U>>;

4.0

2020 年 8 月 20 日
1. 可变元组类型

在JavaScript中有一个函数concat,它接受两个数组或元组并将它们连接在一起构成一个新数组

function concat(arr1, arr2) {
  return [...arr1, ...arr2];
}

假设有一个tail函数,它接受一个数组或元组并返回除首个元素外的所有元素。

function tail(arg) {
  const [_, ...result] = arg;
  return result;
}

那么,我们如何在TypeScript中为这两个函数添加类型?
在旧版本的TypeScript中,对于concat函数我们能做的是编写一些函数重载签名

function concat(arr1: [], arr2: []): [];
function concat<A>(arr1: [A], arr2: []): [A];
function concat<A, B>(arr1: [A, B], arr2: []): [A, B];
function concat<A, B, C>(arr1: [A, B, C], arr2: []): [A, B, C];
function concat<A, B, C, D>(arr1: [A, B, C, D], arr2: []): [A, B, C, D];
function concat<A, B, C, D, E>(arr1: [A, B, C, D, E], arr2: []): [A, B, C, D, E];
function concat<A, B, C, D, E, F>(arr1: [A, B, C, D, E, F], arr2: []): [A, B, C, D, E, F];)

在保持第二个数组为空的情况下,我们已经编写了七个重载签名接下来,让我们为arr2添加一个参数。

function concat<A2>(arr1: [], arr2: [A2]): [A2];
function concat<A1, A2>(arr1: [A1], arr2: [A2]): [A1, A2];
function concat<A1, B1, A2>(arr1: [A1, B1], arr2: [A2]): [A1, B1, A2];
function concat<A1, B1, C1, A2>(arr1: [A1, B1, C1], arr2: [A2]): [A1, B1, C1, A2];
function concat<A1, B1, C1, D1, A2>(arr1: [A1, B1, C1, D1], arr2: [A2]): [A1, B1, C1, D1, A2];
function concat<A1, B1, C1, D1, E1, A2>(arr1: [A1, B1, C1, D1, E1], arr2: [A2]): [A1, B1, C1, D1, E1, A2];
function concat<A1, B1, C1, D1, E1, F1, A2>(arr1: [A1, B1, C1, D1, E1, F1], arr2: [A2]): [A1, B1, C1, D1, E1, F1, A2];

这已经开始变得不合理了。 不巧的是,在给tail函数添加类型时也会遇到同样的问题。

在受尽了“重载的折磨”后,它依然没有完全解决我们的问题。 它只能针对编写的重载给出正确的类型。 如果我们想要处理所有情况,则还需要提供一个如下的重载:

function concat<T, U>(arr1: T[], arr2: U[]): Array<T | U>;

但是这个重载签名没有反映出输入长度,以及元组元素的顺序
TypeScript 4.0带来了两项基础改动,还伴随着类型推断的改善,因此我们能够方便地添加类型。
第一个改动是展开元组类型的语法支持泛型。 这就是说,我们能够表示在元组和数组上的高阶操作,尽管我们不清楚它们的具体类型。 在实例泛型展开时 当在这类元组上进行泛型展开实例化(或者使用实际类型参数进行替换)时,它们能够产生另一组数组和元组类型。
例如,我们可以像下面这样给tail函数添加类型,避免了“重载的折磨”。

function tail<T extends any[]>(arr: readonly [any, ...T]) {
    const [_ignored, ...rest] = arr;
    return rest;
}

const myTuple = [1, 2, 3, 4] as const;
const myArray = ["hello", "world"];

// type [2, 3, 4]
const r1 = tail(myTuple);

// type [2, 3, 4, ...string[]]
const r2 = tail([...myTuple, ...myArray] as const);

第二个改动是,剩余元素可以出现在元组中的任意位置上 – 不只是末尾位置

type Strings = [string, string];
type Numbers = [number, number];

// [string, string, number, number, boolean]
type StrStrNumNumBool = [...Strings, ...Numbers, boolean];
2. 元组参数标记

以下是常规的元组入参示例

function  foo ( ... args : [ string ,  number ] ) : void  { 
    // ... 
}
foo ( "你好" ,  42 ) ;  // 有效

foo ( "hello" ,  42 ,  true ) ;  // 错误
foo ( "你好" ) ;  // 错误

现在可以为元组做标记,更利于维护,提高可观赏性

type Range = [start: number, end: number];
type Foo = [first: number, second?: string, ...rest: any[]];

在这里插入图片描述

3. 显式声明类型

以下代码,类型推断中可能出现undeinfd。但实际过程中,Math.random至少是0,而0是一个有效的值,从而导致代码编译报错

class Square {
    sideLength;

    constructor(sideLength: number) {
        if (Math.random()) {
            this.sideLength = sideLength;
        }
    }

    get area() {
        return this.sideLength ** 2;
        //     ~~~~~~~~~~~~~~~
        // error! Object is possibly 'undefined'.
    }
}

在4.0可以使用!感叹号声明类型是存在的值,就能够正常编译了

class Square {
    // definite assignment assertion
    //        v
    sideLength!: number;
    //         ^^^^^^^^
    // type annotation

    constructor(sideLength: number) {
        this.initialize(sideLength)
    }

    initialize(sideLength: number) {
        this.sideLength = sideLength;
    }

    get area() {
        return this.sideLength ** 2;
    }
}
4. 转换为可选链

可选链一出来就受到广大开发者的热爱,为了提高大家开发和重构代码的效率,现在有工具可以进行自动转换。
在这里插入图片描述

原文地址:https://blog.csdn.net/qq_42427109/article/details/130533706

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_21046.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注