This is the eleventh day of my participation in the Gengwen Challenge. For more details, see “Gengwen Challenge”

Background: Many times, you want a function or class to support multiple data types

Chestnut, a modification of a print function: the function returns any value passed to it

function log(value: string) :string {
  console.log(value);
  return value;
}
Copy the code

1. Change to accept an array argument

Method 1: Function overload

function log(value: string) :string;

function log(value: string[]) :string[];

function log(value: any) {
  console.log(value);
  return value;
}
Copy the code

Mode 2: Union type

function log(value: string | string[]) :string | string[] {
  return value;
}
Copy the code

2. You want the log function to take arguments of any type

Method 1: The type is any

However, the constraint between types is lost, ignoring that the parameter type and the return value type of the function must be the same

function log(value: any) :any {
  console.log(value);
  return value;
}
Copy the code

Method two: the generic type 👉(View the official document)

1. Define functions by generics – Generic functions

Concept: Data types are not determined in advance, specific types are determined at the time of use

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

// Call mode 1: specify the type of T when calling
console.log(
  log<string[] > ["a"."b"]));// Call method 2: use TS type inference, omit the parameter type of the function -- recommended
console.log(log(["a"."b"]));
Copy the code

2. Define function types by generics — Generic function types

Generics can define either a function or a function type

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

------------- Defines a generic function type using a type alias. The equals sign is similar to the function signature, but */ is omitted from the function name
type Log = <T>(value: T) = > T;

// Generic function implementation
let myLog: Log = log;
Copy the code

or

function identity<T> (arg: T) :T {
  return arg;
}

let myIdentity: <T>(arg: T) = > T = identity;

let myIdentity1: <U>(arg: U) = > U = identity;
Copy the code

Generic interfaces

In this case, generics constrain only one function, but we can also use generics to constrain other members of the interface

// This is exactly the same as the type alias
interface Log {
  <T>(value: T): T;
}
Copy the code

Use generics to constrain other members of an interface: Place the generics after the interface name so that all members of the interface can be constrained by the generics

interface Log<T> {
  (value: T): T;
}
Copy the code

Note: After a generic variable restricts the entire interface, a type must be specified during implementation

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

interface Log<T> {
  (value: T): T;
}

// Error message: generic type 'Log
      
       ' requires one type parameter. ts(2314)
      
let myLog: Log = log;

/ / to solve
let myLog1: Log<number> = log;
// myLog1 can only be a number argument
myLog1(1);
Copy the code

You can also specify a default type in the interface definition

// Specify the default type
interface Log<T = Array<string>> {
  (value: T): T;
}

let myLog: Log = log;

myLog(["1"."2"]);
Copy the code

Generics can also constrain the members of a class

A generic class

By placing a generic variable after the class name, you can restrict all members of the class. Note: Generics cannot restrict static members of a class

class Test<K> {
  // Error message: Static members cannot reference class type parameters. ts(2302)
  // static eat(param:T){

  // }

  run(value: K) {
    returnvalue; }}Copy the code

Instance methods will be constrained by generics

/ / instantiate
let log1 = new Test<number> ();Error message: parameter of type "a" cannot be assigned to parameter of type "number". ts(2345)
// log1.run('a')

log1.run(1);
Copy the code

When no argument is specified, the argument type of an instance method can be arbitrary

let log2 = new Test();

log2.run("a");

log2.run(1);
Copy the code

Generic constraint

function testA<T> (value: T) :T {
  // Attribute "length" does not exist on type "T". ts(2339)
  console.log(value, value.length);

  return value;
}
Copy the code

Solution: T inherits the Length interface, indicating that T is constrained, that is, the input parameter must have the Length attribute

interface Length {
  length: number;
}

function testA<T extends Length> (value: T) :T {
  console.log(value, value.length);

  return value;
}

testA([1]);

testA("12212");

testA({ length: 1 });

/* Error message: type "{a: number; } "cannot be assigned to an argument of type" Length ". Object literals can specify only known properties, and "a" is not in type "Length". ts(2345) */
// testA({a:1})
Copy the code

Benefits of generics

  1. Functions and classes can easily support multiple types, enhancing the extensibility of the program
  2. No need to write multiple function overloads, lengthy two union type declarations, enhanced code readability
  3. Flexibly control constraints between types