After beta and RC releases, Typescript 4.4 was released in 8.26. Here’s what’s new:

Process control type inference supports Aliased Conditions and Discriminants

Because javascript is a weakly typed language, we often treat a variable differently depending on its type when using javascript. When such logic is written in typescript, typescript recognizes type checks in your conditional statements (called type guard type sentinels in typescript) for type inference. Here’s an example from the official Release Note:

function foo(arg: unknown) {
    if (typeof arg === "string") {
        console.log(arg.toUpperCase()); }}Copy the code

In this case, the arg argument is of unknown type, but the if statement uses javascript typeof statements to determine the typeof the argument. Typescript can infer that the arg argument is of type string within the block of the if statement. The toUpperCase method of String should be called naturally.

But what if we assign the type determination from the if statement to a variable?

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

Prior to version 4.4, typescript type validation reported errors.

In version 4.4, such typescript code is fine. Typescript finds that when we judge a constant in flow control, we do some extra work to see if the constant itself previously contained some type checking logic. This is suitable for scenarios where type verification is performed on const variables, readonly attributes, and function parameters that have not been reassigned.

Here are some examples:

class Test {
    readonly name: unknown ='readonlyString'
    writableName: unknown = 'writableNameString'
}

function foo(arg: unknown, arg2: unknown) {
  const argIsString = typeof arg === "string";
  const a = argIsString; // I can assign one more time here
  if (a) {
      console.log(arg.toUpperCase());
  }

  const b = new Test();
  const type = typeof b.name === 'string'
  const wriatbleType =  typeof b.writableName === 'string'

  if (type) {
      console.log(b.name.toUpperCase());
  }

  if (wriatbleType) {
  		WriatbleType is not a readonly property
      console.log(b.writableName.toUpperCase());
      / / ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
      // Error! Property 'toUpperCase' does not exist on type 'unknown'.
  }
	
  arg2 = arg;
  const arg2IsString = typeof arg2 === "string";
  if (arg2IsString) {
    Type inference in control flow is invalid because arg2 has been reassigned
    console.log(arg2.toUpperCase());
    / / ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
    // Error! Property 'toUpperCase' does not exist on type 'unknown'.}}Copy the code

In addition to typeof’s type check condition statements, other type checks work. The official document uses the discriminated Union as an example:

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

function area(shape: Shape) :number {
    const isCircle = shape.kind === "circle";
    if (isCircle) {
        // 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; }}Copy the code

In this example, Shape is a discriminated Union type. Typescript can now infer that shape variables belong to a specific type of discriminated Unions in if conditional statements by judging shape.kind === ‘circle’. As a result, shape can obtain radius attributes without type exceptions in an if(isCircle) block, which was not supported in previous versions of typescript.

In typescript 4.4, type inference for discriminated Unions goes a little further — you can extract the attributes of the discriminated Unions variable and use conditional statements. Typescript also does type inference correctly.

Official examples:

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; }}Copy the code

In fact, I did not understand the meaning of “more in-depth” here, students who understand can express their opinions, the original is

Analysis on discriminants in 4.4 also goes a little bit deeper

In addition, two examples are given to show that type inference does a lot to upgrade the code for process control. I look at these two examples is a little moved.

function doSomeChecks(
    inputA: string | undefined,
    inputB: string | undefined,
    shouldDoExtraWork: boolean.) {
    let mustDoWork = inputA && inputB && shouldDoExtraWork;
    if (mustDoWork) {
        // We can access 'string' properties on both 'inputA' and 'inputB'!
        const upperA = inputA.toUpperCase();
        const upperB = inputB.toUpperCase();
        In previous versions you needed to write this
        // const upperA = inputA! .toUpperCase();
        // const upperB = inputB! .toUpperCase();
        // And if you are obsessive-compulsive, you should also disable this rule for ESLint
        // @typescript-eslint/no-non-null-assertion}}Copy the code

This example is equivalent to typescript understanding the logic of the first two amps of an if statement, so we don’t need the non-null operator (inputA! . inputB! .). To call the toUpperCase function.

// I changed this example a bit, because there are no common prototype methods for Boolean types
function f(x: string | number | boolean) {
  const isNumber = typeof x === "number";
  const isBoolean = typeof x === "boolean";
  const isBooleanOrNumber = isNumber || isBoolean;
  if (isBooleanOrNumber) {
      x;  // Type of 'x' is 'boolean | number'.
  }
  else {
      x.replace('s'.'a');  // Type of 'x' is 'string'.
    	In previous versions you needed to write this
    	// (x as string).replace('s', 'a');}}Copy the code

In this example, typescript successfully deduces that x can only be string in the else branch, so you can call the string prototype method directly. In older versions of typescript, the as method is used.

So the typescript type system is getting better and better at type inference, and we used to think, “Can’t you do that? There are fewer and fewer places that are so stupid.

Finally, the author makes the following attempt to strengthen the discriminated Union type

type PersonInfo =
  | { sex: "male".gameCost: number.cost: { game:number| {}}sex: "female".clothesCost: number.cost: { clothes:number}};function statistics(info: PersonInfo) :number {
  const { sex: sexInfo, cost } = info;
  if (sexInfo === 'male') {
      statisticsInfo = cost.game;
    	/ / Error!!!!! ~ ~ ~ ~
      // type "{game: number; } | { clothes: number; } "does not exist on the property" game ".
  }
  else {
      statisticsInfo = cost.clothes;
    	// Error!!           ~~~~~~~
      // type "{game: number; } | { clothes: number; } "does not exist on the property" clothes ".
  }

  const { sex } = info;
  if (sex === 'male') {
      const { cost: costInfo }= info
      statisticsInfo =  costInfo.game;
  }
  else {
      const { cost }= info
      statisticsInfo = cost.clothes;
  }
  return statisticsInfo;
}
Copy the code

It is found that if the cost type of the info is extracted in advance, the type cannot be correctly inferred in the conditional branch block. The reason has time to goimplementWhy is there a pigeon here)

Class static code block (static blocks)

Typescript supports the syntax for static blocks of classes. Static block syntax is currently an ECMAScript proposal, currently at StagE3, and the next stage is formal inclusion into the language specification.

That static code block is what ghost, I believe that ordinary people are less used. The author also compares Java syntax to understand:

  • It is executed once as the class is loaded. In particular, static code blocks are invoked by classes. Class is called with a static block of code executed first.
  • A static code block is essentially a class initialization.
  • Variables in static code blocks are local variables, no different in nature from local variables in ordinary functions.
  • You can have more than one static code block in a class

Typically in static code blocks, to execute some class-initialization code. In a static block of code, you can write all kinds of statements, even try catches. Here are some examples:

class Foo {
    static #count = 0;

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

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

This is an example of using a try catch in a static block of code.

let getX: (obj: C) = > X;

type X = {data: number}

export class C {
  #x: X
  static #y = 0;
  constructor(x: number) {
    console.log('class C constructor block');
    this.#x = { data: x };
  }

  static gety() {
    return this.#y;
  }

  static {
    // getX has privileged access to #x
    console.log('class C static block');
    getX = (obj) = >obj.#x; }}export function readXData(obj: C) {
  return getX(obj).data;
}

C.gety()
const c = new C(1);
const c2 = new C(2);
console.log(readXData(c));
console.log(readXData(c2));

// Final output order:
// class C static block
// class C constructor block
// class C constructor block
/ / 1
/ / 2
Copy the code

This example is to illustrate the order in which static code is fast and build functions are executed.

TSC — help

A little.

Performance optimization

A little.

Embedded prompt

Typescript 4.4 gives your code more hints. Such as:

  • Prompt for the name of the function argument when a function is called

Defines a callName function with a parameter named name. When you call the callName function, you are prompted that the currently passed variable is the name parameter of the function definition (shown in red). This tip is useful when we call a function that defines many parameters.

  • Indicates the return type of the function

The code does not define the return value of the callName function. That’s equal to let me write it

** note: ** this prompt needs to be set in Vscode’s setting. Search the typescript. InlayHints

Breaking Changes

Attention!!

Lib. Which s changes

Base operation do not 6. The specific change is here: lib.d.ts change from 4.3 to 4.4. If there are some cases in the later actual project where the common usage is wrong and the code needs to be changed manually, the author will add them here. I haven’t found them yet.

The error parameter type of the catch statement is changed to unknown

The type of the error argument after the catch in the try catch statement is changed from any to unknown. If your previous project was in strict mode and you use the Error attribute directly in the catch block, you will get an Error.

Property 'message' does not exist on type 'unknown'.
Property 'name' does not exist on type 'unknown'.
Property 'stack' does not exist on type 'unknown'.
Copy the code

Solution 1

Go straight to any big method:

try{}catch (error: any) {
	console.log(error.message)
}
Copy the code

Solution 2

Do not use strict mode. Set strict to false in the tsconfig.json file.

Solution 3

Increase useUnknownInCatchVariables configuration

Perfect the check for always true promises

In typescript 4.3, a feature has been added to check for promises that always return true. Such as:

async function foo() :Promise<boolean> {
    return false;
}

async function bar() :Promise<string> {
    const fooResult = foo();
    if (fooResult) {        // <- error! :D
        return "true";
    }
    return "false";
}
Copy the code

Typescript detects that when a function return type is defined as Promise and its return value is always true using conditional statements, typescript considers such code problematic and tells you to forget to await it.However, in version 4.3, if you write it this way, such a check won’t work.

async function foo() :Promise<boolean> {
  return false;
}

async function bar() :Promise<string> {
  if (foo()) {        // <- no error
      return "true";
  }
  return "false";
}
Copy the code

So in version 4.4, this feature has been improved.

Class abstract properties

In version 4.4, abstract properties and functions do not allow initial assignment and will report an error.

abstract class C {
    abstract prop = 1;
    / / ~ ~ ~ ~
    // Property 'prop' cannot have an initializer because it is marked abstract.
}
Copy the code

This shouldn’t matter much, because if you know people who use abstract classes, abstract properties, and abstract functions, you probably won’t initialize them.

Next. Typescript 4.5

Some features planned for version 4.5: TypeScript 4.5 Iteration Plan

refs

Announcing the TypeScript 4.4

The last

Search Eval Studio on wechat for more updates.