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

news2024/11/19 20:42:08
(在人生的道路上,当你的期望一个个落空的时候,你也要坚定,要沉着。——朗费罗)

在这里插入图片描述

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. 转换为可选链

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

K8s之Namespace名称空间详解

文章目录 一、名称空间概念二、名称空间基本操作1、创建Namespace的两种方式2、Namespace资源限额 一、名称空间概念 K8s中文手册&#xff1a; K8s名称空间中文官方手册&#xff1a; Kubernetes 支持多个虚拟集群&#xff0c;它们底层依赖于同一个物理集群。 这些虚拟集群被…

UUID使用

1.UUID是什么&#xff1f; UUID 是指&#xff08;Universally Unique Identifier&#xff09;通用唯一识别码&#xff0c;128位。RFC 4122描述了具体的规范实现。 2.UUID解决问题&#xff1f; 分库分表后自增Id重复问题。 3.UUID作用&#xff1f; UUID 的目的是让分布式系…

[数据库系统] 一、创建表以及使用主键约束(educoder)

1.任务&#xff1a;在数据库中创建一个表。 2.需要掌握&#xff1a; 如何在指定数据库中创建表。 知识点&#xff1a;如何在指定数据库中创建表。 我们先来了解一下在数据库中创建表的规则&#xff1a; CREATE TABLE 表名(字段名,数据类型,字段名,数据类型,.....) 例如&…

Oracle 体系结构

文章目录 Oracle体系结构Oracle的内存结构Oracle的进程结构服务器进程后台进程可选后台进程 物理存储结构逻辑存储结构 概念&#xff1a; Oracle server由Oracle instance和Oracle database组成&#xff0c;Oracle instance由后台进程和共享内存组成&#xff0c;Oracle的实例包…

《走进对象村4》之面向对象的第一大特性——封装

文章目录 &#x1f680;文章导读1、封装的概念2、访问限定修饰符3、如何进行封装4、封装的优点&#xff1a; &#x1f680;文章导读 在本篇文章中&#xff0c;将详细的对封装进行总结&#xff0c;文章仅仅是个人的一些理解&#xff0c;如果有错误的地方&#xff0c;还望指出看完…

八岁都能懂:O(N)条件下在N个元素中找出第K小的元素

目录 1 进入情境1-1 金字塔道具1-2 感觉还不够1-3 万能筛子1-4 怎么用呢 2 代码实现2-1 伪代码描述2-2 完整实例c 3 引申3-1 完美的折半舍弃3-2 找出前K小的元素(topK方法&#xff09;3-3 O(n)效率下求中位数参考资料 1 进入情境 生日&#xff0c;朋友送了一堆弹珠&#xff0c;…

[PyTorch][chapter31][卷积]

前言&#xff1a; 卷积神经网络 是 计算机视觉里面应用非常广泛的一个模型。 卷积神经网络 成功案例 人脸识别,指纹识别等应用. 卷积的本质是图像的特征提取&#xff0c; 在冈萨雷士版本《数字图像处理》处理有专门的章节讲解 各种卷积核&#xff0c;在通讯原理里面&#xff0…

MOD09A1、MYD09A1 文件说明

总体介绍 文件全称为 MODIS Terra/Aqua Surface Reflectance 8-Day L3 Global 500 m。文件名称的示例为 “MOD09A1.A2000337.h11v05.005.2006342055602.hdf”。下载下来的 hdf 包括 13 个子数据集&#xff0c;一个 tile 包括 2400*2400 个像元。各数据集的性质如下表所示。 Sci…

51单片机基础实验1

一.51单片机 51单片机&#xff0c;也称为AT89C51&#xff0c;是一种基于Intel 8051架构的8位单片机。它是广泛应用于嵌入式系统和微控制器领域的常见芯片之一。其具有高度灵活性、可编程性和可靠性&#xff0c;因此被广泛应用于各种应用场景中。 51单片机的主要特点包括&…

三十、组播技术——IGMP、IGMP-snooping、PIM-DM、PIM-SM

文章目录 组播概述1、IP组播通信概念2、组播和单播ip地址3、组播MAC4、组播应用场景5、PIM、IGMP、IGMP-snooping 一、IGMP 协议二、PIM DM&#xff08;密集模式&#xff09;1、配置示例如下&#xff1a;2、PIM DM的局限性 三、PM SM&#xff08;稀疏模式&#xff09; 组播概述…

【python学习】基础篇-文件与系统-os 模块及 os.path 相对路径与绝对路径

在 Python 中&#xff0c;内置了os 模块及其子模块 ospath&#xff0c;用于对目录或文件进行操作。 在使用 os 模块或者 os.path 模块时&#xff0c;应首先使用 import 语句将其导入&#xff0c;然后才可以使用它们提供的函数或方法。 1、常用操作目录函数 2.相对路径与绝对路…

如何手写一个支持H.265的高清播放器

概述 音视频编解码技术在当前的互联网行业中十分热门&#xff0c;特别是高清视频播放器的开发&#xff0c;其中包括4K、8K等超高清分辨率的播放器&#xff0c;具有极高的市场需求和广泛的应用场景。H265编码技术更是实现高清视频压缩的重要手段之一。如果想要掌握音视频编解码…

微服务部署:蓝绿发布、滚动发布、灰度发布、金丝雀发布

前言 在项目迭代的过程中&#xff0c;不可避免需要上线。上线对应着部署&#xff0c;或者重新部署&#xff1b;部署对应着修改,修改则意味着风险。 1.蓝绿发布(Blue/Green Deployment) ①定义 蓝绿部署是不停老版本&#xff0c;部署新版本然后进行测试。确认OK后将流量切到新…

ADSP21489之CCES开发笔记(十一)

一、主模式固件加载&#xff1a; 1、激活SPICLK信号&#xff0c;并将SPI_FLG0_O引脚拉低。 2、将读取命令0x03和24位地址0x000000写入从设备。如图24-4所示。 图24-4 二、PCAG时钟选择与配置。 1、来源晶振 2、来源Pin脚 其中来源Pin脚配置PCAG时&#xff0c;需将PCG_CTLx1上加…

如何生成api接口获取宝贝商品详情,商品详情接口,产品详情

API (Application Programming Interface)是指应用程序接口&#xff0c;它是一种通过编写一组统一的规则&#xff0c;开发一个软件来与其他应用程序进行通讯的技术。API可以方便应用程序之间的交流和数据共享&#xff0c;以及增强应用程序的功能。 在现代应用程序中&#xff0…

23.自定义指令

像是 v-if,v-for,v-model 这些是官方指令&#xff0c;vue允许开发者自定义指令 目录 1 mounted 1.1 基本使用 1.2 第一个形参 1.3 第二个形参 2 updated 3 函数简写 4 全局自定义指令 1 mounted 当指令绑定到元素身上的时候&#xff0c;就会自动触发mounted()…

MySQL---基本操作DQL(基本查询语法,算术运算符,比较运算符,逻辑运算符,位运算符)

1. 基本查询语法 select [all|distinct] <目标列的表达式1> [别名], <目标列的表达式2> [别名]... from <表名或视图名> [别名],<表名或视图名> [别名]... [where<条件表达式>] [group by <列名> [having <条件表达式>]] [o…

【Mysql】数据更新过程redo log、脏页 到磁盘文件

简介 Mysq 执行一条SQL记录时&#xff0c;会首先将数据写入 redo log&#xff0c;然后更新到内存上&#xff0c;等到满足特定条件之后&#xff0c;才将数据写入到数据库磁盘文件。 redo log也保存在磁盘上&#xff0c;和数据库磁盘文件的区别&#xff0c;在于写入的方式。red…

asp.net+sqlserver企业财务管理系统

1 系统概述5 1.1 研究的背景5 1.2 研究的意义5 1.3 主要研究内容6 2 系统开发环境6 2.1开发工具介绍6 2.1.1 Visual Studio介绍6 2.1.2 SQL Server介绍7 2.1.3 IIS介绍8 2.2 动态网站技术介绍8 2.3开发模式B/S介绍8 3 需求分析9 3.1 需求分析9 3.1.1系统用户需求9 3.1.2系统功能…

Vue学习笔记2 - Vue常用指令

上一章 【Vue学习笔记2 -naxVue是什么 】讲了Vue的基本概念。 本章来学习一下Vue常用指令。 1&#xff0c;v-text 绑定文本 2&#xff0c;v-html 绑定 html 3&#xff0c;v-for for循环 4&#xff0c;v-show 显示/隐藏 5&#xff0c;v-if/v-esle-if/v-else 条件式 6&…