Take you to see the new features of TypeScript 5.0

1. Write in front

TypeScript 5.0 has been released on March 16, 2023, bringing many new features and optimizing performance. Let's take a look at the more important changes in the new version of TypeScript.

2. New features

2-1. Speed ​​and package volume optimization

The first is the performance improvement of the new version. Version 5.0 has a good optimization in terms of build speed and package volume. The following table shows the performance improvement of version 5.0 compared to 4.9:

project The degree of optimization relative to TS 4.9
material-ui build time 90%
TypeScript compiler startup time 89%
TypeScript compiler self-build time 87%
Outlook Web build time 82%
VS Code build time 80%
npm package size 59%

What exactly does TypeScript 5.0 do to optimize performance?

  • The first is to namespacemigrate to module, which applies more features of modern build tools to optimize (such as scope hoisting) and remove some deprecated code, reducing the bundle size by about 26.4 MB.
  • Secondly, TS simplifies the data stored in the objects inside the compiler and reduces the memory usage.
  • Then it is optimized in some specific areas, such as occasional use in closures varinstead of letor constto improve parsing performance.

Overall, most TypeScript projects see a 10% to 20% performance boost from TypeScript 5.0.

2-2. New decorator standard

Decorators are familiar to those who write ts. Although it is not a standard js feature, ts already supports "experimental" decorators in previous versions. In the latest version 5.0, the decorator syntax will no longer be an "experimental" syntax, and there is no need to add --experimentalDecoratorsconfiguration items to the compilation options (although in the new version, this compilation option will still exist), TypeScript 5.0 Will natively support decorator syntax!

Let's look at an example of a decorator -

First we have a very simple class:

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

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

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

We want to greetadd some logs to the functions of this class and record the name of the calling function, so the easiest way is to do this:

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

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

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

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

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

But if we want to add similar functions to more functions, it is very suitable to use decorators. For example, we can write one loggedMethod, as follows:

function loggedMethod(
  originalMethod: any,
  context: ClassMethodDecoratorContext
) {
    
    
  const methodName = String(context.name);

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

  return replacementMethod;
}

In this way, we can use loggedMethodthis decorator to decorate this function to achieve the above effect:

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

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

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

// Output:
//
//   LOG: Entering method 'greet'.
//   Hello, my name is zy.
//   LOG: Exiting method 'greet'.

We can even create a "function that returns the decorator function", so that we can develop more customized and more functions for the decorator, for example, I want to customize the prefix of the string output to the console:

function loggedMethod(headMessage = "LOG:") {
    
    
  return function actualDecorator(
    originalMethod: any,
    context: ClassMethodDecoratorContext
  ) {
    
    
    const methodName = String(context.name);

    function replacementMethod(this: any, ...args: any[]) {
    
    
      console.log(`${
      
      headMessage} Entering method '${
      
      methodName}'.`);
      const result = originalMethod.call(this, ...args);
      console.log(`${
      
      headMessage} Exiting method '${
      
      methodName}'.`);
      return result;
    }

    return replacementMethod;
  };
}

In this way, we loggedMethodcall it before it is used as a decorator, so that we can pass in a custom string as the prefix of the console output string:

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

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

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

// Output:
//
//   LOG: Entering method 'greet'.
//   Hello, my name is zy.
//   LOG: Exiting method 'greet'.

2-3. constType parameters

When inferring the type of an object, ts will usually choose a generic type. For example, in this case namesthe type of is inferred as 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"] });

There is no problem with the inference string [], but because namesit is readonlyyes, but the inferred type is not readonly, this will cause some troubles. Although we can as constfix this by adding, like so:

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

But it's cumbersome to write like this, and it's easy to forget. So in TypeScript 5.0, we can add constmodifiers directly to type parameter declarations, turning constant type inference into default values:

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

Specific details: https://github.com/microsoft/TypeScript/pull/51865

2-4. extendsConfiguration items support multiple configuration files

extendsConfiguration items support multiple configuration files is actually one of the most practical features in TypeScript 5.0 in my opinion. When we use tsconfig.jsonto manage multiple projects, in many cases, configuration extensions will be performed on a "baseline" configuration file, such as the following:

// packages/front-end/src/tsconfig.json
{
    
    
    "extends": "../../../tsconfig.base.json",
    "compilerOptions": {
    
    
        "outDir": "../lib",
        // ...
    }
}

However, in many cases, we will want to extend from multiple configuration files, but TypeScript 4.9 and previous versions do not support this feature! And the good news is that Typescript 5.0 now allows the extends field to introduce multiple configuration paths. For example, the following way of writing:

// tsconfig1.json
{
    
    
    "compilerOptions": {
    
    
        "strictNullChecks": true
    }
}

// tsconfig2.json
{
    
    
    "compilerOptions": {
    
    
        "noImplicitAny": true
    }
}

// tsconfig.json
{
    
    
    "extends": ["./tsconfig1.json", "./tsconfig2.json"],
    "files": ["./index.ts"]
}

In this example, strictNullChecksboth and noImplicitAnywill take effect in the final tsconfig.json.

Note that if there are field conflicts in these imported configuration files, the fields imported later will overwrite the fields imported earlier.

Specific details: https://github.com/microsoft/TypeScript/pull/50403

2-5. All enumerations become joint enumerations

When TypeScript originally designed enumeration types, they were just a set of numeric constants with the same type, such as the following enumeration E:

enum E {
    
    
  Foo = 10,
  Bar = 20,
}

E.Foo, E.BarCompared with ordinary variables, the only special thing is that it can be assigned to anything of type E. Other than that, they are almost numbersindistinguishable from types, like this:

function takeValue(e: E) {
    
    }

takeValue(E.Foo); // works
takeValue(123); // error!

Enums didn't become special until TypeScript 2.0 introduced the enum literal type. Enum literal types give each enum member its own type, and turn the enum itself into a collection of each member type. They also allow us to refer to only a subset of enumerated types and narrow the scope of those types, as shown in the enumeration below Color:

// Color is like a union of Red | Orange | Yellow | Green | Blue | Violet
enum Color {
    
    
    Red, Orange, Yellow, Green, Blue, /* Indigo */, Violet
}

// Each enum member has its own type that we can refer to!
type PrimaryColor = Color.Red | Color.Green | Color.Blue;

function isPrimaryColor(c: Color): c is PrimaryColor {
    
    
    // Narrowing literal types can catch bugs.
    // TypeScript will error here because
    // we'll end up comparing 'Color.Red' to 'Color.Green'.
    // We meant to use ||, but accidentally wrote &&.
    return c === Color.Red && c === Color.Green && c === Color.Blue;
}

And in some scenarios—for example, when enumeration members are initialized with function calls, TypeScript cannot calculate the set of enumeration values, and it will abandon the joint enumeration and use the old enumeration strategy instead.

TypeScript 5.0 solves this problem by turning all enums into union enums by creating a unique type for each enum member. This way, our enum values ​​in all cases, will be union enums.

Specific details: https://github.com/microsoft/TypeScript/pull/50528

2-6. --moduleResolutionConfiguration item support bundleroptions

TypeScript 4.7 introduces and options in --moduleand configuration items . These options better mimic the lookup rules for ECMAScript modules in Node.js. However, this pattern has many limitations, for example, in ECMAScript modules in Node.js, any relative imports need to include the file extension:--moduleResolutionnode16nodenext

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

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

But with the development of front-end technology, this search rule has become outdated. Most modern packaging tools use a fusion of ECMAScript module and CommonJS module lookup rules in Node.js. So to emulate the way bundling tools work, TypeScript now introduces a new strategy: --moduleResolution bundler.

{
    
    
    "compilerOptions": {
    
    
        "target": "esnext",
        "moduleResolution": "bundler"
    }
}

If you are using packaging tools like Vite, esbuild, swc, Webpack, Parcel, etc., then bundlerthis configuration will be very suitable.

Specific details: https://github.com/microsoft/TypeScript/pull/51669

2-7. Supportexport type *

When TypeScript 3.8 introduced type import/export export _ from "module", export _ as xx from "module"type exports like or were not allowed. TypeScript 5.0 added support for these two type export syntaxes:

// 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'.
}

For details, see: https://github.com/microsoft/TypeScript/pull/52217

Guess you like

Origin blog.csdn.net/u011748319/article/details/129670957