“Don’t be afraid, don’t settle, work hard in the future” — Hello! I am little sesame ๐Ÿ˜„

Generics is the property of specifying the type of a function, interface, or class without specifying the specific type in advance

1. Generic functions

  • First, we implement a function createArray that creates an array of specified length and populates each entry with a default value
function createArray(length: number, value: any) :Array<any> {
  let result: any = []
  for (let i = 0; i < length; i++) {
    result[i] = value
  }
  return result
}
let result = createArray(3.'x')
console.log(result) // ['x', 'x', 'x']
Copy the code

Array

allows each entry in the Array to be of any type, but we expect that each entry in the Array should be of the type of the input value. Where generics come in handy:

function createArray<T> (length: number, value: T) :Array<T> {
  let result: Array<T> = []
  for (let i = 0; i < length; i++) {
    result[i] = value
  }
  return result
}
let result = createArray<string> (3.'x')
console.log(result) // ['x', 'x', 'x']
let result2 = createArray<number> (3.3) // T is an argument. What is passed is what
console.log(result2) / / [3, 3, 3]

Copy the code

In the example above, we add

to the function name, where T is used to refer to any input type, which can be used in the input value: T and output Array

.

You can then specify that it is of type string or number when called. Of course, it is possible to let type inference be calculated automatically instead of specifying it manually:

createArray(3.'x'); // ['x', 'x', 'x']
Copy the code

The genericTScope is limited to function internal use

2. Generic classes

The use of generics in classes

class MyArray<T> {
  private list: T[] = []
  add(val: T) {
    this.list.push(val)
  }
  getMax(): T {
    let result: T = this.list[0]
    for (let i = 0; i < this.list.length; i++) {
      if (this.list[i] > result) {
        result = this.list[i]
      }
    }
    return result
  }
}
let arr = new MyArray<number>()
arr.add(1)
arr.add(2)
arr.add(3)
let result3 = arr.getMax()
console.log(result3) / / 3
Copy the code

3. Generic interfaces

You can also specify generics when defining interfaces

  • Use an interface with generics to define the shape of an object:
interface Cart<T> {
  list: T[]
}
let cart: Cart<number> = {
  list: [1.2.3],}Copy the code
  • Use an interface with generics to define the shape of a function:
interface CreateArrayFunc<T> {
  (length: number.value: T): Array<T>;
}

let createArray: CreateArrayFunc<string>; // When using a generic interface, you need to define the type of the generic.
createArray = function<T> (length: number, value: T) :Array<T> {
  let result: T[] = [];
  for (let i = 0; i < length; i++) {
      result[i] = value;
  }
  return result;
}

let result = createArray(3.'x'); 
console.log(result)// ['x', 'x', 'x']
Copy the code

4. Multiple type parameters

When defining generics, you can define more than one type parameter at a time:

function swap<A.B> (tuple: [A, B]) :B.A]{
  return [tuple[1], tuple[0]];
}
let result = swap<string.number> (['Little Golden Sesame'.18]);
console.log(result);// [18, 'Golden little Sesame ']
Copy the code

In the example above, we defined a swap function that swaps input tuples.

5. Default generic types

After TypeScript 2.3, we can specify default types for type parameters in generics. This default type comes into play when type parameters are not specified directly in the code when using generics and cannot be inferred from the actual value parameters.

function createArray<T = number> (length: number, value: T) :Array<T> {
  let result: Array<T> = []
  for (let i = 0; i < length; i++) {
    result[i] = value
  }
  return result
}
let result = createArray(3.'x')
console.log(result)
let result2 = createArray(3.3)
console.log(result2)
Copy the code

6. Generic constraints

  • When using generics in functions, the specific type is not known in advance, so the methods of that type cannot be accessed
function logger<T> (val: T) :T {
    console.log(val.length); // There is no attribute "length" on ERROR type "T".
    return val;
}
Copy the code

In the example above, the generic T does not necessarily contain the attribute length, so the error was reported during compilation.

At this point, we can constrain the generics to only allow the function to pass in variables that contain the length attribute. This is the generic constraint:

interface LengthWith {
  length: number
}
function logger<T extends LengthWith> (val: T) {
  console.log(val.length) / / 5
  return val;
}
logger('Little Golden Sesame')
Copy the code

In the example above, we use extends to constrain that the generic T must conform to the shape of the LengthWith interface, that is, it must contain the Length attribute.

If val does not contain length when calling logger, error will be reported at compile time:

logger(1) // An argument of ERROR type "1" cannot be assigned to an argument of type "LengthWith".
Copy the code

Multiple type parameters can also constrain each other:

function copyFields<T extends U.U> (target: T, source: U) :T {
    for (let id in source) {
        target[id] = (<T>source)[id];
    }
    return target;
}
let x = { a: 1.b: 2.c: 3.d: 4 };
copyFields(x, { b: 10.d: 20 });
Copy the code

In the example above, we use two type parameters, which require T to inherit from U. This ensures that U does not have fields that do not exist in T.

Generic type aliases

  • Generic type aliases can express more complex types
type Cart2<T> = { list: T[] } | T[]
let c1: Cart2<string> = { list: ['1']}let c2: Cart2<string> = ['1']
Copy the code

reference

[1].typescript Tutorial