This is the sixth day of my participation in the First Challenge 2022. For details: First Challenge 2022.

We can omit the type designation at some point, and TS will help us infer the appropriate type where the omitted type is.

We can understand the inference rules of TS by learning type inference.

Type inference is designed to accommodate the flexible nature of JS so that, in some cases, only compatible types can pass detection.

A simple example

Let’s start with a basic example:

let name = 'dylan';
name = 123; // Error: Type "number" cannot be assigned to type "string".
Copy the code

We define a name attribute, but do not specify the type, just give it a string value, TS will help us infer that name probably wants a string type. So when we assign name to a value of a numeric type, we get an error.

2. Multi-type combination

let arr = [1.'a'];
Copy the code

We define an array arr with two elements: numeric type 1 and string type A.

TS type inference is: will the Array Array < number | string >

If we want to assign another value to the array (with a value other than number or string), we will get an error.

arr = [1.2.3.'a'.true]; / / Error: cannot assign type "Boolean" type "string | number".
Copy the code

Type compatibility

interface Infos {
  name: string;
}
let infos: Info;
const infos1 = { name: 'dylan' };
const infos2 = { age: 18 };
const infos3 = { name: 'dylan'.age: 18 };
Copy the code

We define an Infos interface, an unassigned parameter Infos, and three assigned parameters infos1, infos2, and infos3

Now we’re going to assign infOS with these three assigned objects.

infos = infos1;
infos = infos2; // Error: type "{age: number; }" missing attribute "name" in"
infos = infos3; // More, not less
Copy the code
  • infos1infosAssignment (OK) :infos1In full compliance withInfosThe contents of the interface definition
  • infos2infosError:infos1The lack ofnamefield
  • infos3infosAssignment (OK) :infos3There is anameThe field. Assignment requirements:The value of the assignmentThere must beThe value assignedIn all required fields, more than the indifferent.

The compatibility checks here are deeply recursive.

Function compatibility

1. Parameter number

let x = (a: number) = > 0;
let y = (b: number, c: string) = > 0;
Copy the code

Here we define two functions, x and y, which differ only in parameters.

Now let’s do the assignment:

y = x; // OK
Copy the code

You can see that there’s no problem assigning x to y.

Let’s try assigning y to x:

y = x; // Error: Type "(b: number, C: string) => number" cannot be assigned to type "(a: number) => number".
Copy the code

You can see that the error is now reported.

TS requires that the number of arguments of the function on the right be less than or equal to the number of arguments of the function on the left.

Let’s look at a practical example:

const arr = [1.2.3];
arr.forEach((item, index, array) = > {
  console.log(item)
})
Copy the code

We define an array arR and call the array forEach method. We know that forEach receives a callback that takes three parameters: the current item, the index, and the array itself.

You can see that the code above actually uses only the first argument (the current item). Therefore, we generally ignore the following two items when we generally use them:

arr.forEach(item= > {
  console.log(item);
})
Copy the code

Use the above code to call forEach.

Now we can understand the previous example: forEach required three callback arguments, but we only used one. So the number of arguments to our callback function must be less than or equal to the number of arguments to the function being assigned.

2. Parameter types

let x = (a: number) = > 0;
let y = (b: string) = > 0;
x = y; // Error: The types of parameters "b" and "a" are incompatible.
Copy the code

Two functions of different argument types cannot be assigned to each other: the types are incompatible

3. Optional and remaining parameters

const getSum = (arr: number[].callback: (. args:number[]) = > number) :number= > {
  returncallback(... arr); };Copy the code

Define a getSum function

  • Parameter 1 isAn array of type number;
  • Argument 2 is a callback function (argument is aAn array of type number, the return value isnumberType);
  • The return value isnumberType.

Next we call getSum:

const result = getSum([1.2.3], (... args:number[]) :number= > {
  return args.reduce((pre, cur) = > pre + cur, 0);
})
console.log(result); / / 6
Copy the code

Corresponding to the above code is to set up fixed parameters

const result2 = getSum([1.2.3], (arg1: number.arg2: number.arg3: number) :number= > {
  return arg1 + arg2 + arg3;
})
Copy the code

As you can see, Result is a little more flexible than Result2.

When the function argument to be assigned contains the remaining arguments (as in the above code… Args), the assigned function can be replaced with any number of arguments (the type needs to be the same).

Meanwhile, the remaining parameters can be regarded as innumerable optional parameters.

4. Bidirectional covariant of function parameters

let funcA = (arg: number | string) :void= > {};
let funcB = (arg: number) :void= > {};
Copy the code

Having defined two functions, funcA and funcB, we now assign:

funcA = funcB; / / can
funcB = funcA; / / can also
Copy the code

Either assignment is ok.

We can see that funcA can be either number or string; FuncB takes an argument of type number. Both functions can take a single argument (type number)

5. Return value type

let x = (): string | number= > 0;
let y = (): string= > 'a';
x = y; // OK
y = x; / / Error: cannot type "string | number" assigned to type "string".
Copy the code

We have no problem assigning y to x, because y is a string, and x can be either a string or a number.

You can’t just assign x to y the other way around.

Function overloading

function merge(arg1: number, arg2: number) :number;
function merge(arg1: string, arg2: string) :string;
function merge(arg1: any, arg2: any) { // The actual function body does not count as part of the function overload
  return arg1 + arg2;
}
Copy the code

Fifth, enumeration compatibility

Numeric enumeration types and numeric types are compatible.

enum Status {
  On,
  Off,
}
let s = Status.On;
s = 2; // OK: it is compatible with numeric types
Copy the code

If we define an enumeration again

enum Animal {
  Dog,
  Cat
}
Copy the code

And I’m going to assign s

s = Animal.Dog; // Error: Type "animal. Dog" cannot be assigned to type "Status".
Copy the code

Error: Status.On and animal. Dog are both 0, but they are incompatible.

Therefore, numeric enumerations are compatible only with numeric types, not between different enumerations.

Class compatibility

Only members of instances are compared. Class static members and constructors are not compared.

Let’s define three classes:

class AnimalClass {
  public static age: number;
  constructor(public name: string){}}class PeopleClass {
  public static age: string;
  constructor(public name: string){}}class FoodClass {
  constructor(public name: number){}}Copy the code

Define three variables to use the above class as a type:

let animal: AnimalClass
let people: PeopleClass;
let food: FoodClass;
Copy the code

When a class is used to specify the type of a variable, TS checks for instances

animal = people; // OK
animal = food; // Error: The type of attribute "name" is incompatible.
Copy the code

Although the type of the AnimalClass and PeopleClass static attribute age is different, classes as types do not detect static members and constructors, only members on instances are compared. Both instances add a name attribute to the instance via public Name: String, and both are of type String, so the types of the two classes are compatible.

Similarly, an instance member of FoodClass has a name attribute, but is of type number, so it is incompatible.

Private and protected

Using the private and protected modifiers affects class compatibility.

When checking instances of a class for compatibility, if the target type (to which the value is assigned) contains private members, then the original type (to which the value is assigned) must contain private members from the same class.

class ParentClass {
  private age: number;
  constructor(){}}class ChildrenClass extends ParentClass {
  constructor() {
    super();
  }
}
class OtherClass {
  private age: number;
  constructor(){}}Copy the code

If you look at the class definition, ParentClass and OtherClass are identical except for the class name.

Next we create an example test:

const children: ParentClass = new ChildrenClass(); // OK: subclasses can assign values to superclass types
const other: ParentClass = new OtherClass(); // Error: Type "OtherClass" cannot be assigned to type "ParentClass". A type has a separate declaration of the private property "age".
Copy the code

Protected is the same as private

Compatibility with generics

Generics contain type parameters, which can be of any type.

When used, the type parameter is specified as a specific type that affects only the part of the type parameter that is used

interface Data<T> {}
let data1: Data<number>;
let data2: Data<string>;
data1 = data2; // OK: the function block is empty inside.
Copy the code

But if you define a real thing for the interface:

interface Data<T> {
  data: T
}
let data1: Data<number>;
let data2: Data<string>;
data1 = data2; // Error: Type "Data
      
       " cannot be assigned to type "Data
       
        ".
       
      
Copy the code

Now the assignment will get an error because data1 requires a data attribute of type number; Data2 requires a data property of type string. That’s when they’re incompatible.