Basic essence of TypeScript

Keywords: node.js Attribute Java Javascript REST

Original address: Basic essence of TypeScript

github address of basic notes: https://github.com/qiqihaobenben/Front-End-Basics Either watch or star.

Type considerations

Array type

There are two types of annotation, with particular attention to the second using the TS built-in Array generic interface.

let arr1: number[] = [1,2,3]
// The following is implemented by using the Array generic interface built in TS
let arr2: Array<number | string> = [1,2,3,"abc"]

Tuple type

Tuple is a special array, which limits the number and type of array elements

let tuple: [number, string] = [0, "1"];

We need to pay attention to the cross-border problem of Yuanzu. Although elements can be added across the border, they still cannot be accessed across the border. It is strongly not recommended to use this method.

tuple.push(2)  // No mistake
console.log(tuple) // [0, 1, 2] can also be printed out
console.log(tuple[2]) // But if you want to take out the out of bounds elements in the tuple, you will report that the length of the tuple is 2, and there is no element when the index is 2.

Function type

The function type can be defined first and then used. In the specific implementation, parameters and return value types are not required to be specified, and the parameter name is not required to be the same as that in the definition.

let compute: (x: number, y: number) => number;
compute = (a, b) => a + b;

object type

Object if you want to assign or modify property values, you cannot use simple object types. You need to define complete object types.

let obj: object = { x: 1, y: 2 };
obj.x = 3; // An error will be reported. It's just a simple definition of the object type, but there are no attributes in it.

// You need to change to the following object type definition
let obj: { x: number; y: number } = { x: 1, y: 2 };
obj.x = 3;

symbol type

Symbol type can be declared as symbol type or assigned directly. Like ES6, two declared symbols are not equal.

let s1: symbol = Symbol();
let s2 = Symbol();
console.log(s1 === s2)  // false

undefined, null type

Variables can be declared undefined and null, but once declared, no other types can be assigned.

let un: undefined = undefined;
let nu: null = null;
un = 1 // Report wrong
nu = 1 // Report wrong

undefined and null are subtypes of any type, which can be assigned to other types. But configuration item 'strictnullchecks' needs to be set: false

// Set 'strictnullchecks': false
let num: number = 123;
num = undefined;
num = null;

// But it is more recommended to set num as the union type
let num: number | undefined | null = 123;
num = undefined;
num = null;

Enumeration type

Enumerations are divided into numerical enumerations and string enumerations, as well as heterogeneous enumerations (not recommended)

Numeral enumeration

Enumeration can be accessed by name and index. Let's see how to get it.

enum Role {
  Reporter = 1,
  Developer,
  Maintainer,
  Owner,
  Guest
}
Role.Reporter = 2 // Enumeration members are read-only and cannot be modified and reassigned

console.log(Role)
//Print out: {1: "Reporter", 2: "Developer", 3: "Maintainer", 4: "Owner", 5: "Guest", Reporter: 1, Developer: 2, Maintainer: 3, Owner: 4, Guest: 5}
//We can see that the printed object is an object, in which the index value is the key and the name is the key. Therefore, enumeration can take value through both the name and the index.

// Take a look at how the TS compiler implements enumerations with reverse mapping.
"use strict";
var Role;
(function (Role) {
    Role[Role["Reporter"] = 1] = "Reporter";
    Role[Role["Developer"] = 2] = "Developer";
    Role[Role["Maintainer"] = 3] = "Maintainer";
    Role[Role["Owner"] = 4] = "Owner";
    Role[Role["Guest"] = 5] = "Guest";
})(Role || (Role = {}));

String Enum

String enumeration can only be obtained by name, not by index.

enum Message {
  Success = 'Success',
  Fail = 'fail'
}
console.log(Message)
// Print out: {Success: 'Success', Fail:' failure '}
// We see that only the name is the key, indicating that string enumeration cannot be reversed mapped.

Constant enumeration

Enumerations declared with const are constant enumerations, which are removed during compilation. The following code does not generate code after compilation and can only be used before compilation. When we do not need an object but need the value of an object, we can use constant enumeration, which can reduce the compiled code.

const enum Month {
  Jan,
  Feb,
  Mar
}
let month = [Month.Jan, Month.Feb, Month.Mar];

Heterogeneous enumeration

Number and string enumeration are mixed, not recommended

enum Answer {
  N,
  Y = 'Yes',
  // C, / / the enumeration member after the string enumeration member must be assigned an initial value
  //  X = Math.random() / / computed values are not allowed in enumerations with string members
}

Enumeration member notes

  • Enumeration members are read-only and cannot be modified and reassigned
  • Enumeration members are divided into const member and computer member

    • const member, including the case of no initial value, reference to existing enumeration members and constant expression, will calculate the result at compile time and appear in the form of constant in the runtime environment.
    • computer member, the enumeration member to be calculated, will not be calculated in the compilation phase, and will be reserved to the execution phase of the program.
  • For enumeration members after calculated member, an initial value must be assigned, otherwise an error will be reported.
  • computer member is not allowed in enumeration with string member, and the enumeration member after string enumeration member must be assigned an initial value, otherwise an error will be reported (see heterogeneous type above).
  • In numerical enumeration, if two members have the same index, the latter index will overwrite the former (see enumeration number below)

     // Enumeration members
     enum Char {
       // const member constant enumeration, the result will be calculated in the compilation phase, and it will appear in the runtime environment as a constant.
       a,
       b = Char.a,
       c = 1 + 3,
    
       // Calculated member the enumeration members to be calculated will not be calculated in the compilation phase, but will be reserved in the execution phase.
       d = Math.random(),
       e = '123'.length,
       // For enumeration members after calculated member, an initial value must be assigned, otherwise an error will be reported.
       f = 1
     }
     console.log(Char)
    
     // Enum number
     enum number { a = 1, b = 5, c = 4, d }
     console.log(number) //Print out {1: "a", 4: "c", 5: "d", a: 1, b: 5, c: 4, d: 5}
     // The initial value of b is 5, and the initial value of c is 4. The index of D is 5. When the indexes are the same, the following values will overwrite the previous ones, so the corresponding value of 5 is d.

    Enumerations and enumerating members as separate types

    There are three cases: (1) enumeration members have no initial value, (2) enumeration members are all numerical enumeration, (3) enumeration members are all string enumeration

    • Variable is defined as number enumeration type. It is OK to assign any value of number type (it can exceed the number range defined by enumeration), which has no effect on enumeration, but cannot assign string, etc.
    • Different enumeration types cannot be compared, but the same enumeration type can be compared, but different enumeration members of the same enumeration type cannot be compared.
    • Variable is defined as enumeration type. Even if it is defined as the type of a specific member of enumeration type, assignment has no effect on enumeration. (as follows, the results of E and F remain unchanged.)
    • For the assignment of string enumeration type, only enumeration members can be used instead of random assignment. (if lower F)
     enum E { a, b } // Enumeration members have no initial value
     enum F { a = 1, b = 5, c = 4, d } // Enum members are all numeric enumerations
     enum G { a = 'apple', b = 'banana' } // Enumeration members are string enumeration
    
     // Variable is defined as number enumeration type. It is OK to assign any value of number type, which has no effect on enumeration, but cannot assign string, etc.
     let e: E = 3
     let f: F = 3
     // e === f / / different enumeration types cannot be compared, and an error will be reported.
     console.log(E,F,e,f) // Print: {0: "a", 1: "b", a: 0, b: 1}, {1: "a", 4: "c", 5: "d", a: 1, b: 5, c: 4, d: 5}, 3, 3
     // It can be seen that the variable is defined as E,F assignment, which has no effect on E,F enumeration itself.
    
     let e1: E = 3
     let e2: E = 3
     console.log(e1 === e2) // The same enumeration type can be compared, and the result is true.
    
     let e3: E.a = 3
     let e4: E.b = 3
     // e3 === e4 / / different enumeration members of the same enumeration type cannot be compared, and an error will be reported.
     console.log(E,E.a,E.b,e3,e4) // Print: {0: "a", 1: "B", a: 0, B: 1} 0 1 3, visible variable is defined as E.a,E.b assignment, which has no effect on E and E.a,E.b enumeration itself.
    
     //For the assignment of string enumeration type, only enumeration members can be used instead of random assignment.
     let g1: G = 'abc' // Report wrong
     let g2: G = G.a // g2 can assign G.a or G.b.
     let g3: G.a = G.a // g2 can only assign G.a

Interface type

Structure of interface constraint object, function and class

Object type interface

Object redundancy field

When the object type interface directly verifies the literal quantity of the object with redundant fields, it will report an error, which is inevitable sometimes.

 interface List {
   id: number;
   name: string;
 }
 interface Result {
   data: List[];
 }

 function render(result: Result) {
   result.data.forEach((value) => {
     console.log(value.id,value.name)
   })
 }

 render({
   data: [
     {id: 1, name: 'A',sex: 'male'},
     {id: 2,name: 'B'}
   ]
 });
 // This is that the object type interface directly verifies the "object literal" with redundant fields. There will be an error in the above render, saying that the object can only specify known properties, and "sex" is not in the type "List"

Solution 1: declare the variable result outside, and then pass the result into the render function to avoid passing in the literal amount of the object.

// Assign a literal value to a variable first so that detection can be bypassed
 let result = {
   data: [
     {id: 1, name: 'A',sex: 'male'},
     {id: 2,name: 'B'}
   ]
 }
 render(result);

Solution 2: use type assertion (two as and angle bracket), but if there is no matching in the object literal, an error will be reported. You can use as unknown as xxx

 render({
   data: [{ id: 1, name: "A", sex: "male" }, { id: 2, name: "B" }]
 } as Result);

 // But if none of the incoming object literals match, the type assertion will still report an error.
 render({
   data: [{ id: 1, name: "A", sex: "male" }]
 } as Result); // The type of the error property "data" will still be reported as incompatible

 // Now you need to write like this. Use as unknown as xxx
 render({
   data: [{ id: 1, name: "A", sex: "male" }]
 } as unknown as Result);

Solution 3: sign with string index

 interface List {
   id: number;
   name: string;
   [x:string]: any;
 }
 // In this way, the literal of the object can contain any number of string properties.
Interface properties can be defined as read-only and optional
 interface List {
   readonly id: number; // Read-only attribute
   name: string;
   age?: number; // optional attribute
 }

#####Indexable type
You can use indexable types when you are not sure how many properties there are in an interface. It can be divided into digital index signature and string index signature. If an interface defines the value type of an index signature, the value of the property defined later must be a subtype of the type of the signature value. You can use both types of indexes, but the return value of a numeric index must be a subtype of the return value type of a string index.

 interface Names {
   [x: string]: number | string;
   // y: boolean; / / an error will be reported. boolean will not be assigned to the string index type, because the signature type of the string index is number string, so the property to be defined later must be a subtype of the signature value type.
   [z: number]: number; // The digital index signature can also be defined after the string index signature. The return value of the digital index must be a subtype of the return value type of the string index.
 }

Function type interface

interface Add {
  (x: number, y: number): number;
}
// Equivalent to variable declaration: let add: (A: number, B: number) = > number
let add4: Add = (a,b) => a + b

Hybrid interface

For mixed interfaces, it should be noted that there is no order for the attributes in the interface. The first attribute of mixed interfaces is not an anonymous function.

interface Lib {
  version: string;
  ():void;
  doSomething():void;
}
// Type assertion required
let lib: Lib = (() => {}) as Lib;
lib.version = '1.0'
lib.doSomething = () => {}

Interface inheritance

// Here is an example of interface inheritance
interface Human {
  name: string;
  eat(): void;
}
interface Man extends Human {
  run(): void
}
interface Child {
  cry():void
}

interface Boy extends Man, Child {}
let boy: Boy = {
  name: '',
  run(){},
  eat(){},
  cry(){}
}

Function type correlation

There are four ways to define TS functions. The first way can be called directly, but the last three ways need to implement the defined functions before calling.

// First, direct statement
function add1 (x:number, y:number):number {
  return x + y
}
// One to one correspondence between formal parameter and actual parameter in application
add1(1, 2)

// Second variable declaration
let add2: (x:number, y:number) => number
// The application is as follows
add2  = (a, b) => a + b
add2(2, 2)

// Third type alias
type Add3 = (x: number, y: number) => number
// The application is as follows
let add3: Add3 = (a, b) => a + b
add3(3, 2)


// The fourth interface implementation
interface Add4 {
  (x: number, y: number): number;
}
// Equivalent to variable declaration: let add4: (A: number, B: number) = > number
let add4: Add4 = (a,b) => a + b
add4(4, 2)

Optional parameters

Optional parameter must be after the required parameter, i.e. there can be no more required parameters after the selected parameter

// y cannot be followed by a required parameter, so d will report an error.
// function add5(x:number, y?:number, d:number) {

// Correct as follows
function add5(x:number, y?:number) {
  return y? y + x: x
}
add5(1)

Parameter default

The parameter with default value does not need to be placed after the required parameter, but if the parameter with default value appears before the required parameter, you must explicitly pass in the undefined value to get the default value. The parameters with default values after all required parameters are optional. Like optional parameters, they can be omitted when calling functions.

function add6 (x: number, y = 0, z:number,q = 1) {
  return x +y + z +q
}
// The second parameter must pass in the undefined placeholder
add6(1,undefined,2)

function overloading

It is required to define a series of function declarations, and implement overloading in the broadest version. The function overloading of TS compiler will query a list of overloads, and match from the first one. If the matching is successful, it will be directly executed. So we need to write the definition of approximate rate matching in the front.

The declaration of a function overload is only used in the type checking phase and will be removed after compilation.

function add8(...rest: number[]):number
function add8(...rest: string[]):string
function add8(...rest: any[]):any {
  let first = rest[0]
  if(typeof first === 'string') {
    return rest.join('')
  }
  if(typeof first === 'number') {
    return rest.reduce((pre,cur) => pre + cur)
  }
}
add8(1,2,3) // 6
add8('1','2','3') // '123'

class

Class properties and methods considerations

  • Class attributes are instance attributes, not prototype attributes, and class methods are prototype methods
  • The properties of the instance must have an initial value or be initialized in the constructor, except for those of type any.

Class inheritance

The constructor of a derived class must contain a 'super' call, and 'super' must be called before accessing this in the constructor of a derived class“

Class modifier

1. public: visible to all (default).

2. Private: private attribute

Private properties can only be accessed in declared classes, not in subclasses or generated instances. However, private properties can be accessed in instance methods, because they are also equivalent to being accessed in classes, but the instance methods of subclasses cannot be accessed.

The constructor of a class can be defined as a private type, so the class can neither be instantiated nor inherited

3. protected attribute

The protected property can only be accessed in the declared class and its subclasses, but the protected property can be accessed in the methods of the instance, because it is also equivalent to being accessed in the class

The constructor of a class can be defined as a protected type. Then the class cannot be instantiated, but it can be inherited, which is equivalent to the base class.

4. readonly

The read-only property must have an initial value or be initialized in the constructor. After initialization, it cannot be changed. The read-only property with the initial value set can also be reinitialized in the constructor. But it cannot be reinitialized in the constructor of its subclass.

5. Static static attribute

It can only be called by the name of a class, and cannot be accessed in an instance or a constructor or an instance in a subclass. However, static properties can be inherited, and can be accessed by the class name of a subclass.

Note: you can also add modifiers to the parameters of the constructor, which can directly define the parameters as the properties of the class

class Dog {
  constructor(name: string) {
    this.name = name
    this.legs = 4 // Read only properties that already have a default value can be reinitialized
  }
  public name: string
  run() { }
  private pri() { }
  protected pro() { }
  readonly legs: number = 3
  static food: string = 'bones'
}
let dog = new Dog('jinmao')
// dog.pri() / / private attribute can not be invoked in an instance.
// dog.pro() / / protected property cannot be invoked in an instance.
console.log(Dog.food) // 'bones'


class Husky extends Dog {
  constructor(name: string, public color: string) {
    super(name)
    this.color = color
    // this.legs = 5 / / the read-only property of the parent class cannot be reinitialized in the constructor of the child class
    // this.pri() / / a subclass cannot call the private property of the parent class
    this.pro() // A subclass can call a protected property of the parent
  }
  protected age: number = 3
  private nickname: string = 'Two ha'
  info(): string {
    return this.age + this.nickname
  }
  // color: string / / the parameter uses a modifier, which can be directly defined as a property. It is not needed here
}

let husky = new Husky('husky', 'black')
husky.info() // If the method of the called class has access to the private and protected properties of the class, this is error free.
console.log(Husky.food) // The 'bones' subclass can call the static properties of the parent class

abstract class

A class that can only be inherited and cannot be instantiated.

In an abstract class, you can add common methods or abstract methods, which are then implemented by subclasses.

abstract class Animal {
  eat() {
    console.log('eat')
  }
  abstract sleep(): void // Abstract methods, implemented in subclasses
}
// let animal = new Animal() / / an error will be reported. The abstract class cannot create an instance.


class Cat extends Animal {
  constructor(public name: string) {
    super()
  }
  run() { }
  // Abstract methods must be implemented
  sleep() {
    console.log('sleep')
  }
}

let cat = new Cat('jiafei')
cat.eat()

Interface class

  • When a class implements an interface, it must implement all the properties of the interface, but a class can define its own properties
  • An interface cannot constrain a class's constructor, only public members
interface Human {
  // new (name:string):void / / an interface cannot constrain a class's constructor
  name: string;
  eat(): void;
}

class Asian implements Human {
  constructor (name: string) {
    this.name = name
  }
  name: string
  // private name: string / / an error will be reported if the private attribute is used to implement the interface.
  eat() {}
  sleep(){}
}

Interface inheritance class

It is equivalent to abstracting the members of a class, only the member structure of the class, but there is no concrete implementation.

When an interface pulls out a class member, it pulls out not only the public attribute, but also the private attribute and the protected attribute, so the non inherited subclass will report an error.

The subclass of the abstract class can also implement the interface abstracted from the class, and does not need to implement the existing properties of the subclass's parent class

class Auto {
  state = 1
  // protected state2 = 0 / / the following C will report an error, because C is not a subclass of Auto. C just implements the interface abstracted from Auto.
}
interface AutoInterface extends Auto {

}
class C implements AutoInterface {
  state = 1
}

// The subclass of the abstracted class can also implement the interface abstracted from the class, and it does not need to implement the existing properties of the parent class
class Bus extends Auto implements AutoInterface {
  // There is no need to set state. The parent class of Bus already exists.

}

generic paradigm

Generic Functions

Note: when defining function types with generics, the position is not used to determine whether parameter types need to be specified, as shown in the following example.

Examples of generic functions

function log<T>(value: T): T {
  console.log(value)
  return value
}

log<string[]>(['a', 'b'])
log([1, 2]) // The TS can automatically infer without specifying the type

// You can also define generic functions with type aliases
//The following definition does not specify a parameter type
type Log = <T>(value:T) => T // You don't need to specify the parameter type, you will infer by yourself
let myLog: Log = log
//The following definition must specify a parameter type
type Log<T> = (value:T) => T // If you define function types in this way with generics, you must specify a parameter type
let myLog: Log<string> = log

generic interface

function log<T>(value: T): T {
  console.log(value)
  return value
}

// The following only constrains one generic function in the generic interface. The implementation does not need to specify the parameter type of the generic.
interface Log {
  <T>(value: T): T;
}
let myLog: Log = log

// The following constrains the entire generic interface. The implementation needs to specify the parameter type of the generic, or use the generic with the default type.
interface Log1<T> {
  (value: T): T;
}
let myLog1: Log1<string> = log

interface Log2<T = string> {
  (value: T): T
}
let myLog2: Log2 = log

Note: when the generics of a generic interface are defined as global, the implementation must specify a parameter type, or use a generic with a default type

Generic class

class Log3<T> {
  // Static members cannot refer to generic parameters of a class
  // static start(value: T) {
  //   console.log(value)
  // }
  run(value: T) {
    console.log(value)
    return value
  }
}
let log3 = new Log3<number>()
log3.run(1)

//You can pass in any type without specifying a type
let log4 = new Log3()
log4.run('abc')

Note: generics cannot be applied to static members of a class. And when instantiating, you can pass in any type without specifying the type

Generic constraint

Constrains the type passed in by the generic

interface Length {
  length: number
}
function log5<T extends Length>(value: T) {
  // To print out the length attribute of value defined as generic T, t must have the length attribute, so generic constraints are required. After t inherits the length interface, it must have the length attribute.
  console.log(value,value.length)
  return value
}
log5([1])
log5('abc')
log5({length: 1})

Generic summary

  • With generics, functions and classes, you can easily support multiple types to enhance the extensibility of your programs
  • No need to write multiple function overloads, lengthy union type declaration, enhance code readability
  • Constraints between flexible control types

Type checking mechanism

Type checking mechanism: some principles and behaviors of TypeScript compiler in type checking. Its function is to assist development and improve development efficiency

Type inference

Type inference: refers to the type of variable (return value type of function) that does not need to be specified. TypeScript can automatically infer a type for it according to some rules.

Basic type inference

let a = 1 // Inferred as number
let b = [1] // Inferred as number []
let c = (x = 1) => x + 1 // Inferred as (x?: number) = > number

Best generic type inference

When you need to infer a type from multiple types, TypeScript infers a common type compatible with all current types as much as possible

let d = [1, null]
// Inferred as the most compatible type, so inferred as (number | null) []
// When the 'strict nullchecks' configuration item is closed, null is a subtype of number, so it is inferred as number []

Context type inference

All of the above inferences are right to left, that is, based on expression inferences, context type inferences are left to right, which usually occur in event processing.

Type Asserts

When you make sure that you know the type more accurately than ts, you can use type assertion to bypass the check of TS. it is effective to modify the old code, but to prevent abuse.

interface Bar {
  bar: number
}
let foo = {} as Bar
foo.bar = 1

// However, the type should be specified when variable declaration is recommended.
let foo1: Bar = {
  bar: 1
}

Type compatibility

When one type Y can be assigned to another type X, we can say that type X is compatible with type Y

X compatible Y: X (target type) = y (source type)

let s: string = 'a'
s = null // Set strict nullchecks in the compilation configuration to false, and the character type is compatible with null type (because null is a subtype of character)

Interface compatibility

Compatible with fewer members

interface X {
  a: any;
  b: any;
}
interface Y {
  a: any;
  b: any;
  c: any;
}

let x: X = { a: 1, b: 2 }
let y: Y = { a: 1, b: 2, c: 3 }
// The source type can be assigned as long as it has the necessary properties of the target type. Interfaces are compatible with each other, and those with fewer members are more compatible.
x = y
// y = x / / incompatible

Function compatibility

type Handler = (a: number, b: number) => void
function test(handler: Handler) {
  return handler
}
1. Number of parameters
Fixed parameter

The number of parameters of the objective function must be more than that of the source function

Handler target function. The parameter function passed in test is the source function.

let handler1 = (a: number) => { }
test(handler1) // The incoming function can receive a parameter, and the parameter is number, which is compatible.
let handler2 = (a: number, b: number, c: number) => { }
test(handler2) // The function that will report an error can receive three parameters (there are too many parameters), and the parameter is number, which is incompatible.
Optional and remaining parameters
let a1 = (p1: number, p2: number) => { }
let b1 = (p1?: number, p2?: number) => { }
let c1 = (...args: number[]) => { }

(1) fixed parameters are compatible with optional parameters and remaining parameters.

a1 = b1 // compatible
a1 = c1 // compatible

(2) the optional parameters are not compatible with the fixed parameters and the remaining parameters, but the error can be eliminated by setting "strictFunctionTypes": false to achieve compatibility.

b1 = a1 //Incompatible
b1 = c1 // Incompatible

(3) the remaining parameters can be compatible with fixed parameters and optional parameters.

c1 = a1 // compatible
c1 = b1 // compatible
2. Parameter type
Foundation type
// Follow the test function above
let handler3 = (a: string) => { }
test(handler3) // incompatible types
Interface type

If the interface has more compatible members and less compatible members, it can also be understood that the interface is expanded and the compatible parameters with more parameters are less. For incompatibilities, you can also set "strictFunctionTypes": false to eliminate errors and achieve compatibility

interface Point3D {
  x: number;
  y: number;
  z: number;
}
interface Point2D {
  x: number;
  y: number;
}
let p3d = (point: Point3D) => { }
let p2d = (point: Point2D) => { }

p3d = p2d // compatible
p2d = p3d // Incompatible
3. Return value type

The return value type of the target function must be the same as the return value type of the source function, or its subtype

let f = () => ({ name: 'Alice' })
let g = () => ({ name: 'A', location: 'beijing' })
f = g // compatible
g = f // Incompatible
4. Function overload

Function overload list (target function)

function overload(a: number, b: number): number;
function overload(a: string, b: string): string;

Specific implementation of function (source function)

function overload(a: any, b: any): any { }

The parameters of the target function must be more than those of the source function to be compatible

function overload(a:any,b:any,c:any):any {} // The specific implementation parameters are more than those of the first defined function matched in the overload list, that is, the parameters of the source function are more than those of the target function, which is incompatible.

Incompatible return value type

function overload(a:any,b:any) {} // any with return value removed, incompatible

Enumeration type compatibility

enum Fruit { Apple, Banana }
enum Color { Red, Yello }
Enumeration types and number types are fully compatible
let fruit: Fruit.Apple = 4
let no: number = Fruit.Apple
Enumeration types are completely incompatible
let color: Color.Red = Fruit.Apple // Incompatible

Class compatibility

It is similar to the interface, only comparing the structure. It should be noted that when comparing whether two classes are compatible, static members and constructors do not participate in the comparison. If two classes have the same instance members, their instances are compatible with each other.

class A {
  constructor(p: number, q: number) { }
  id: number = 1
}
class B {
  static s = 1
  constructor(p: number) { }
  id: number = 2
}
let aa = new A(1, 2)
let bb = new B(1)
// The two instances are completely compatible, and static members and constructors are not compared
aa = bb
bb = aa
Private attributes

There are two kinds of private properties in a class. If one class has private properties, the other does not. None of them can be compatible. If both classes have, neither class is compatible.

If a class has private properties and another class inherits the class, the two classes are compatible.

class A {
  constructor(p: number, q: number) { }
  id: number = 1
  private name:string = '' // Only add this private attribute to class A. aa is not compatible with bb, but bb is compatible with aa. If private attributes are added to both classes a and B, they are not compatible.
}
class B {
  static s = 1
  constructor(p: number) { }
  id: number = 2
}
let aa = new A(1, 2)
let bb = new B(1)
aa = bb // Incompatible
bb = aa // compatible


// There are private attributes in A. after C inherits a, aa and cc are compatible with each other.
class C extends A { }
let cc = new C(1, 2)
// Instances of both classes are compatible
aa = cc
cc = aa

Generic compatibility

generic interface

When the generic interface is empty, the generic specifies different types and is compatible.

interface Empty<T> {}

let obj1:Empty<number> = {}
let obj2:Empty<string> = {}
// compatible
obj1 = obj2
obj2 = obj1

If there is an interface member in a generic interface, different types are incompatible

interface Empty<T> {
  value: T
}

let obj1:Empty<number> = {}
let obj2:Empty<string> = {}
// Error reporting, incompatible
obj1 = obj2
obj2 = obj1
Generic Functions

Two generic functions are compatible if they are defined the same and no type parameter is specified

let log1 = <T>(x: T): T => {
  return x
}
let log2 = <U>(y: U): U => {
  return y
}
log1 = log2
log2 = log1

Compatibility summary

  • Compatibility between structures: less members, more members
  • Compatibility between functions: more parameters, less parameters

Type protection mechanism

TypeScript can guarantee that a variable belongs to a specific type in a specific block (type protection block). You can safely reference properties of this type in this block, or call methods of this type.

Pre code, after which the code runs

enum Type { Strong, Week }

class Java {
  helloJava() {
    console.log('hello Java')
  }
  java: any
}

class JavaScript {
  helloJavaScript() {
    console.log('hello JavaScript')
  }
  javaScript: any
}

To implement the getLanguage method, whether lang.helloJava exists or not will be used as a judgment, and an error will be reported.

function getLanguage(type: Type, x: string | number) {
  let lang = type === Type.Strong ? new Java() : new JavaScript()

  // If you want to judge the existence of lang.helloJava directly according to the type of Lang instance, you will report an error, because now Lang is a joint type of Java and JavaScript.
  if (lang.helloJava) {
    lang.helloJava()
  } else {
    lang.helloJavaScript()
  }
  return lang
}

Using the previous knowledge, we can use type assertion to solve

function getLanguage(type: Type, x: string | number) {
  let lang = type === Type.Strong ? new Java() : new JavaScript()

  // Here we need to use type assertion to tell TS what type of current lang instance it is.
  if ((lang as Java).helloJava) {
    (lang as Java).helloJava()
  } else {
    (lang as JavaScript).helloJavaScript()
  }
  return lang
}

The first method of type protection, instanceof

function getLanguage(type: Type, x: string | number) {
  let lang = type === Type.Strong ? new Java() : new JavaScript()

  // instanceof can judge which class an instance belongs to, so that TS can judge.
  if (lang instanceof Java) {
    lang.helloJava()
  } else {
    lang.helloJavaScript()
  }
  return lang
}

The second method of type protection, in, can determine whether an attribute belongs to an object.

function getLanguage(type: Type, x: string | number) {
  let lang = type === Type.Strong ? new Java() : new JavaScript()

  //  in can determine whether a property belongs to an object, such as helloJava and java.
  if ('java' in lang) {
    lang.helloJava()
  } else {
    lang.helloJavaScript()
  }
  return lang
}

The third method of type protection, typeof type protection, can help us judge the basic types.

function getLanguage(type: Type, x: string | number) {
  let lang = type === Type.Strong ? new Java() : new JavaScript()

  // x is also a joint type. Type of type protection can determine the basic type.
  if (typeof x === 'string') {
    x.length
  } else {
    x.toFixed(2)
  }
  return lang
}

The fourth method of type protection is to determine the type of object by creating a type protection function.

The return value of the type protection function is a little different. It uses is, which is called type predicate.

function isJava(lang: Java | JavaScript): lang is Java {
  return (lang as Java).helloJava !== undefined
}
function getLanguage(type: Type, x: string | number) {
  let lang = type === Type.Strong ? new Java() : new JavaScript()

  // Determine the type of object by creating a type protection function
  if (isJava(lang)) {
    lang.helloJava()
  } else {
    lang.helloJavaScript()
  }
  return lang
}

summary

Different judgment methods have different use scenarios:

  • Type of: judge the type of a variable (mostly used for basic types)
  • instanceof: judge whether an instance belongs to a class
  • in: judge whether an attribute belongs to an object
  • Type protection function: some judgments may not be able to be handled by a statement, requiring more complex logic and suitable for encapsulation into a function

Advanced type

Cross type

Use the & symbol. Although it is called cross type, it is the union of all types taken.

interface DogInterface {
  run(): void
}

interface CatInterface {
  jump(): void
}

// The & symbol is used for cross type. Although it is called cross type, it is the union of all types taken.
let pet: DogInterface & CatInterface = {
  run() { },
  jump() { }
}

Joint type

The type of declaration is uncertain. It can be one of several types. In addition to the types specified in TS, there are string literal union type and number literal union type.

let a: number | string = 1;

// String literal union type
let b: 'a' | 'b' | 'c' = 'a'

// Numeric literal union type
let c: 1 | 2 | 3 = 1

Object union type

The joint type of an object can only take the properties shared by both, so the joint type of an object can only access the intersection of all types.

// Connected to DogInterface and CatInterface above
class Dog implements DogInterface {
  run() { }
  eat() { }
}

class Cat implements CatInterface {
  jump() { }
  eat() { }
}
enum Master { Boy, Girl }
function getPet(master: Master) {
  // pet is the joint type of Dog and Cat, which can only take the common attributes of both, so the joint type can only access the intersection of all types at this time.
  let pet = master === Master.Boy ? new Dog() : new Cat()
  pet.eat()
  // pet.run() / / unable to access, error will be reported
  return pet
}

Distinguishable union types

This mode is a type protection method combining union type and literal type. If a type is a combination of multiple types and each type has a common attribute, we can create different types of protection blocks with this common attribute.

The core is to use two or more types of common attributes to create different code protection blocks

If there are only two joint types of Square and Rectangle in the following functions, there is no problem. However, once the Circle type is added in the extension, the type verification will not work normally and no error will be reported. At this time, we hope that the code will give an error warning.

interface Square {
  kind: "square";
  size: number;
}
interface Rectangle {
  kind: 'rectangle';
  width: number;
  height: number;
}
interface Circle {
  kind: 'circle';
  r: number;
}
type Shape = Square | Rectangle | Circle

// If there are only two joint types of Square and Rectangle in the following functions, there is no problem, but once the Circle type is added in the extension, it will not work normally and will not report an error. At this time, we hope that the code will have an error warning.
function area(s: Shape) {
  switch (s.kind) {
    case "square":
      return s.size * s.size;
      break;
    case "rectangle":
      return s.width * s.height;
      break;
  }
}

console.log(area({ kind: 'circle', r: 1 }))
// undefined, no error is reported. At this time, we hope the code will give an error warning.

If you want to get the correct error notification, the first method is to set a clear return value, and the second method is to use the never type.

The first method is to set a clear return value

// Error will be reported: the function is missing the end return statement, and the return type does not include "undefined"
function area(s: Shape): number {
  switch (s.kind) {
    case "square":
      return s.size * s.size;
      break;
    case "rectangle":
      return s.width * s.height;
      break;
  }
}

The second method is to use the never type. The principle is to write a function in the last default judgment branch, set the parameter to never type, and then pass in the parameter of the outermost function. Normally, it will not execute to the default branch.

function area(s: Shape) {
  switch (s.kind) {
    case "square":
      return s.size * s.size;
      break;
    case "rectangle":
      return s.width * s.height;
      break;
    case "circle":
      return Math.PI * s.r ** 2;
      break;
    default:
      return ((e: never) => { throw new Error(e) })(s)
      //This function is used to check whether s is of the never type. If s is of the never type, all the previous branches are covered. If s is not of the never type, the previous branches are missing, which needs to be supplemented.
  }
}

Index type

Query operators of index type

keyof T Presentation type T Literal union type for all public properties of

interface Obj {
  a: number;
  b: string;
}
let key: keyof Obj
// The type of key is the union type of Obj attributes a and b: let key: "a" | "b"

Index access operator

T[K] represents the type represented by property K of object t

interface Obj {
  a: number;
  b: string;
}
let value: Obj['a']
// The type of value is the type of attribute a of Obj: let value: number

Generic constraint

The T extends U generic variable can inherit a type to get some properties

Let's look at the following code snippets.

let obj = {
  a: 1,
  b: 2,
  c: 3
}

//If the following functions access properties that do not exist in obj, there is no error.
function getValues(obj: any, keys: string[]) {
  return keys.map(key => obj[key])
}

console.log(getValues(obj, ['a', 'b']))
console.log(getValues(obj, ['e', 'f']))
// [undefined, undefined] is displayed, but the TS compiler does not report an error.

Resolved as follows

function getValuest<T, K extends keyof T>(obj: T, keys: K[]): T[K][] {
  return keys.map(key => obj[key])
}
console.log(getValuest(obj, ['a', 'b']))
// Console. Log (getValue (obj, ['e ','f'])) / / an error will be reported.

mapping type

You can generate a new type from an old type

The following code uses the TS built-in mapping type

interface Obj {
  a: string;
  b: number;
  c: boolean
}

// The following three types are called homomorphisms, which only affect the properties of Obj and do not introduce new properties.
//Make all properties of an interface read-only
type ReadonlyObj = Readonly<Obj>
//Make all properties of an interface optional
type PartialObj = Partial<Obj>
//Can extract a subset of interfaces
type PickObj = Pick<Obj, 'a' | 'b'>

// Non homomorphism creates new attributes
type RecordObj = Record<'x' | 'y', Obj>
// Create a new type and introduce the specified new type as
// {
//     x: Obj;
//     y: Obj;
// }

Condition type

T extends U ? X : Y

type TypeName<T> =
  T extends string ? "string" :
  T extends number ? "number" :
  T extends boolean ? "boolean" :
  T extends undefined ? "undefined" :
  T extends Function ? "function" :
  "object"

type T1 = TypeName<string> // The resulting type is: type T1 = "string"
type T2 = TypeName<string[]> // The resulting type is: type T2 = "object"

Distributed condition type

(A | B) extends U ? X : Y Equivalent to (A extends U ? X : Y) | (B extends U ? X : Y)

// Connect with above
type T3 = TypeName<string | string[]> // The type obtained is: type T3 = "string" | "object"

Usage 1: Diff operation can be realized by using distributed conditional type

type T4 = Diff<"a" | "b" | "c", "a" | "e"> // That is: type T4 = "b" | "c"
// Split the steps
// Diff<"a","a" | "e"> | Diff<"b","a" | "e"> | Diff<"c", "a" | "e">
// The distribution results are as follows: never | "b" | "c"
// Finally, get the literal union type "b" | "c"

Usage 2: filter out null and undefined values based on Diff.

type NotNull<T> = Diff<T, undefined | null>
type T5 = NotNull<string | number | undefined | null> // Namely: type T5 = string | number

The above type aliases have built-in types in the TS class library

  • Diff => Exclude<T, U>
  • NotNull => NonNullable<T>

In addition, there are many built-in types, such as extract < T, U >

type T6 = Extract<"a" | "b" | "c", "a" | "e"> // That is: type T6 = "a"

For example: the return value type used to extract function type ReturnType < T >

First, write out the implementation of ReturnType < T >. infer represents the type variable to be inferred in the extends conditional statement.

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

Analyze the above code. First, it is required that the T passed in ReturnType can be assigned to the broadest function. Then, judge whether T can be assigned to a function that can accept the return value of any parameter to be inferred as R. if it can, return the return value to be inferred as R. if not, return any.

type T7 = ReturnType<() => string> //That is: type T7 = string

Posted by onegative on Wed, 16 Oct 2019 21:05:07 -0700