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

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

在这里插入图片描述

TypeScript

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

特性

  1. typescript是javascript的超集,向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 []固然没有问题,但由于names是readonly的,而推断出的类型不是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 time90%
TypeScript Compiler startup time89%
Playwright build time88%
TypeScript Compiler self-build time87%
Outlook Web build time82%
VS Code build time80%
typescript npm Package Size59%

4.9

2022 年 11 月 15 日

1. accessor 关键字支持

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

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

accessor 关键字可以为该属性在运行时转换为一对 get 和 set 访问私有支持字段的访问器。

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 && typeof packageJSON === "object") {
        // Check to see if it has a string name property.
        if ("name" in packageJSON && 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 & 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. 转换为可选链

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/19084.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2023鲁大师评测沟通会:鲁大师尊享版登场、“鲁小车”正式上线

作为硬件评测界的“老兵”&#xff0c;鲁大师不仅有着十几年的硬件评测经验&#xff0c;并且一直都在不断地尝试、不断地推陈出新。在5月9日举行的“2023年鲁大师评测沟通会”上&#xff0c;鲁大师向大众展示了在过去一年间取得的成果。 PC业务迭代升级&#xff0c;鲁大师客户端…

干货满满!破解FP安全收款难题

怎样安全收款是做擦边产品卖家比较忧虑的问题&#xff0c;2023年已经即将来到了年中&#xff0c;跨境卖家们在这一方面做得怎么样了呢&#xff1f; 这期分享破解FP独立站收款难题的方法。 一、商家破解FP收款难题方法 1.第三方信用通道 优点&#xff1a;信用卡在国外使用率比…

好家伙,又一份牛逼笔记面世了...

最近网传的一些裁员的消息&#xff0c;搞的人心惶惶。已经拿到大厂offer的码友来问我&#xff1a;大厂还能去&#xff0c;去了会不会被裁。 还在学习的网友来问我&#xff1a;现在还要冲互联网么&#xff1f; 我是认为大家不用恐慌吧&#xff0c;该看啥看啥&#xff0c;该学啥…

ASEMI代理ADI亚德诺ADUM3211TRZ-RL7原厂芯片

编辑-Z ADUM3211TRZ-RL7参数描述&#xff1a; 型号&#xff1a;ADUM3211TRZ-RL7 数据速率&#xff1a;10 Mbps 传播延迟&#xff1a;50 ns 脉冲宽度失真&#xff1a;3 ns 脉冲宽度&#xff1a;100 ns 输出上升/下降时间&#xff1a;2.5 ns 供电电流&#xff1a;2.6 mA …

Maven与spring学习

目录 该如何学习Maven&#xff0c;是先该学习spring还是先学习Maven 能讲一下该如何学习Maven吗&#xff1f; 火狐浏览器有能让网页翻译成为中文的插件吗 秋田和柴犬是同一个狗吗 该如何学习Maven&#xff0c;是先该学习spring还是先学习Maven 学习Maven可以与学习Spring同…

一键安装k8s脚本

服务器配置 节点(华为云服务器)配置master 2vCPUs | 4GiB | s6.large.2 CentOS 7.8 64bit node1 2vCPUs | 8GiB | s6.large.4 CentOS 7.8 64bit node2 2vCPUs | 8GiB | s6.large.4 CentOS 7.8 64bit 1.master节点安装脚本&#xff1a;install_k8s_master.sh。 sh文件上传到…

2023数维杯数学建模ABC题思路分析

占个位置吧&#xff0c;开始在本帖实时更新数维杯数学建模赛题思路代码&#xff0c;文章末尾获取&#xff01; 持续为更新参考思路 赛题思路 已完成全部可以领取&#xff0c;详情看文末&#xff01;&#xff01;&#xff01; 数维杯A题思路 A题是这次比赛比较难的题目&…

windows下升级nodejs

重新安装新版nodejs 重新安装nodejs然后设置环境变量 安装yarn npm install -g yarn --registryhttps://registry.npm.taobao.org yarn config set registry https://registry.npm.taobao.org -g yarn config set sass_binary_site http://cdn.npm.taobao.org/dist/node-sa…

大数据如何助力营销(2)用户画像

用户画像是指根据用户的数据&#xff0c;构建出用户的特征和兴趣&#xff0c;从而对用户进行分类和个性化的过程。用户画像可以帮助营销人员更有效地触达目标客户&#xff0c;提高营销效果和转化率。 用户画像的价值 用户画像的价值主要体现在以下几个方面&#xff1a; 提升用…

信号signal编程测试

信号会打断系统调用&#xff0c;慎用&#xff0c;就是用的时候测一测。 下面是信号的基础测试 信号 信号&#xff08;signal&#xff09;机制是UNIX系统中最为古老的进程之间的通信机制。它用于在一个或多个进程之间传递异步信号。信号可以由各种异步事件产生&#xff0c;例如…

计算机毕业论文内容参考|基于三维建模和卷积神经网络的人脸转正的技术设计

文章目录 导文文章重点摘要前言绪论课题背景国内外现状与趋势课题内容相关技术与方法介绍技术分析技术设计人脸转正方法卷积神经网络的训练和优化数据预处理技术实现总结与展望本文总结导文 基于java开发汽车销售系统资料 文章重点 摘要 在实际应用中,人脸图像往往具有旋转、…

vue+elementui+nodejs校园高校餐厅点餐及订餐菜品推荐评价系统6927k

传统的销售模式&#xff0c;在实体店的紧跟式的销售模式&#xff0c;会给消费者一种不自由&#xff0c;被监视的感觉。餐厅点餐及推荐系统&#xff0c;紧跟数据时代的步伐&#xff0c;使用nodejs开发语言&#xff0c;配备MySQL数据库。扎根于实际问题所开发出来的一套系统。这个…

【C进阶】-- 字符串函数(1)

目录 0. 前言 1. 函数介绍 1.1 strlen 1.1.1主动改变\0的位置 ✅"strlen函数的返回类型是size_t - 无符号整型"✅ 当使用strlen函数但不引用头文件时&#xff0c;执行结果超出预料: 求字符串长度的方法&#x1f4a5; 1.计数器 2.递归 3.指针 - 指针 1.2 st…

项目集战略一致性

项目集战略一致性是识别项目集输出和成果&#xff0c;以便与组织的目标和目的保持一致的绩效领域。 本章内容包括&#xff1a; 1 项目集商业论证 2 项目集章程 3 项目集路线图 4 环境评估 5 项目集风险管理战略 项目集应与组织战略保持一致&#xff0c;并促进组织效益的实现。为…

粒子群算法(PSO)

理论&#xff1a; 粒子群优化算法&#xff08;PSO&#xff09;是一种智能优化算法&#xff0c;也是一种元启发式算法&#xff0c;最初是由Eberhart和Kennedy提出的&#xff0c;其模拟了鸟群捕食行为&#xff0c;通过一定的搜索策略&#xff0c;使得多个粒子在多维搜索空间中寻…

Java+Python+Paddle提取长文本文章中词频,用于Echart词云图数据

公司有个需求&#xff0c;就是需要提供给echart词云图的数据&#xff0c;放在以前我们的数据来源都是从产品那直接要&#xff0c;产品也是跑的别的接口&#xff0c;那怎么行呢&#xff0c;当然有自己的一套可以随便搞了&#xff0c;那么操作来了 Java package cn.iocoder.yud…

第十四届蓝桥杯大赛软件赛省赛(Java 大学A组)

蓝桥杯 2023年省赛真题 Java 大学A组 试题 A: 特殊日期  试题 B: 与或异或  试题 I: 高塔 把填空挂上跟大伙对对答案&#xff0c;然后 I \rm I I 题出的还行就先讲讲&#xff0c;剩下的最近有点忙&#xff0c;先放放。 试题 A: 特殊日期 本题总分&#xff1a;5 分 【问题描…

PMP课堂模拟题目及解析(第5期)

41. 项目的混凝土供应商通知项目经理&#xff0c;材料将比预定时间晚三个星期交付。项目经理更新了进度计划并通知项目团队。在这种情况下&#xff0c;哪种合同类型承担的风 险最小&#xff1f; A. 总价加激励费用合同。 B. 总价加经济价格调整合同。 C. 工料合同。 D. 固…

利用阿里云免费部署openai的Chatgpt国内直接用

背景 国内无法直接访问ChatGPT&#xff0c;一访问就显示 code 1020。而且最近OpenAI查的比较严格&#xff0c;开始大规模对亚洲地区开始封号&#xff0c;对于经常乱跳IP的、同一个ip一堆账号的、之前淘宝机刷账号的&#xff0c;账号被封的可能性极大。 那么有没有符合openai规定…

PLC与无线开关量测控终端之间Modbus通信实例

本方案是基于Modbus RTU协议下实现的1主多从自组网无线通信形式&#xff0c;主站为S7-1200 PLC&#xff0c;DTD433H作为从站。DTD433H具备输入和输出开关量信号功能&#xff0c;信号传输方向由用户原系统主从设备所实现的功能决定。方案中采用无线开关量信号测控终端DTD433H与欧…