In software engineering, we not only need to create well-defined apis, but we also need to think about reuse, and generics give us that flexibility but elegance

Generic Hello World

Start by defining a function that returns any value passed to it.

function getValue(a: number) :number {
  return a;
}
Copy the code

The function argument we defined above is of a numeric type, and the return value is of a numeric type.

If we need to pass a string argument and return it, we must define another function

function getValue(a: number) :number {
  return a;
}

function getValue1(a: string) :string {
  return a;
}
Copy the code

However, both functions have the same function, except that the types of arguments are different.

And if you want to extend other data types, do you have to write them again?

Alternatively, we use any to define functions:

function getValue(a: any) :any {
  return a;
}
Copy the code

But using any loses some information: the value passed in must be of the same type as the value returned. Because any doesn’t show this relationship.

But ** generics ** can define a consistent interface, with code reuse in mind

Here we use a type variable, which is a special type of variable that represents only the type, not the value

Just keep the same letters, usually T for Type.

function getValue<T> (a: T) :T {
  return a;
}
getValue<number>(3);
getValue<string>("qzy");
Copy the code

We call this version of the function generic because it can be applied to multiple types

Once we have defined a generic function, we can use it in two ways.

The first is to pass in all the arguments, including the type arguments:

getValue<string>("abc");
Copy the code

The second method is more common. Makes use of type corollaries — that is, the compiler automatically helps us determine the type of T based on the arguments passed in:

getValue(1);
Copy the code

Use generic variables

When creating a function using generics, you must use the type correctly inside the function body

function getValue<T> (a: T) :T {
  console.log(a.length);
  return a;
}
Copy the code

In the example above, we want to access the length attribute of the passed parameter.

But T might be a number type, and number types don’t have length attributes. So the compiler gives an error.

function getValue<T> (a: T) :T {
  // Attribute "length" does not exist on type "T"
  console.log(a.length);
  return a;
}
Copy the code

To do this, we define an interface to describe conditions.

interface LengthProps {
  length: number;
}

function getValue<T extends LengthProps> (a: T) :T {
  console.log(a.length);
  return a;
}

getValue([1.2]); 

getValue(1); // Parameters of type "number" cannot be assigned to parameters of type "LengthProps"

Copy the code

A generic interface

Generic interfaces come in two ways

The first:

interface config {
  <T>(val: T): T;
}
// fn: config
// function must follow the interface definition
let fn: config = function <T> (val: T) :T {
  return val;
};

fn(5);
Copy the code

The second:

interface config<T> {
  (val: T): T;
}

function configFn<T> (val: T) :T {
  return val;
}

// config<string>
// = configFn
let fn: config<string> = configFn;

fn("qzy");

Copy the code

Generic constraints – Type parameters

You can declare one type parameter and it is bound by another type parameter.

For example, now we want to get the property from the object using the property name, and we want to make sure that the property exists on object obj, so we need to use constraints between the two types.

function getProperty<T.K> (obj: T, key: K) {
  // Type "K" cannot be used for index type "T"
  return obj[key];
}

let x = { a: 1.b: 2.c: 3.d: 4 };
getProperty(x,'e');
Copy the code

The intent of our code is to find the key property under obj by passing in a generic object, or some other variable, and then passing in another generic variable.

If we pass in a generic key variable, it’s not necessarily an attribute that exists in the generic obj.

To solve this problem, we can use keyof

function getProperty<T.K extends keyof T> (obj: T, key: K) {
  // Type "K" cannot be used for index type "T"
  return obj[key];
}

let x = { a: 1.b: 2.c: 3.d: 4 };

getProperty(x, "b");

getProperty(x,'e'); / / type "" e" "cannot be assigned to the parameter type" b "" |" a "| |" c "" d" "parameter
Copy the code

K extends Keyof T relates two variables of type