In front-end development, not only do we need to create consistent, well-defined apis, but we also need to consider reusability. The ability of components to support not only current data types but also future data types gives you a lot of flexibility when building large systems. This is where ts generics play a key role.

Generics: Basic usage

(1): Routine use

Now there is a requirement to encapsulate a method, take a parameter (type number), and return a result of the same type.

   const indentify1 = (args: number): number= > {
       return args;
   }
Copy the code

What if the type received is number or string?

   const indentify2 = (args: number | string): number | string= > {
       return args;
   }
Copy the code

What if there are more and more parameter types? Generics work.

    const indentify3 = <T>(args: T): T= > {
        return args;   // T is like a variable that guarantees the same type of input and output parameters.
    }
Copy the code

Generics act as constraints. Generics can provide more than one.

    const indentify4 = 
      
       (args: T, mes: U): T => { return args; // Provide a T and U generic type that declares the input parameter type and specifies the return result type. }
      ,>Copy the code

(2): Generic interface

As with indentify4, what happens to generics if the function returns a collection of types?

Method one: Return a collection of types

    const indentify5 = 
      
       (args: T, mes: U): [T,U] => { return [args, mes]; // Too many constraints, outer constraints on generics}
      ,>Copy the code

Method two: Generic interfaces

You can provide an interface to make the parameters of the interface generic. For example 🌰 : define a person function that accepts name, age, gender, and marriage. The last return.

    interface IPersonView<T, U, V> {
        name: T,
        age: U,
        sex: T,
        isMarray: V
    }
    
    const per = <T, U, V>(name: T, age: U, sex: T, isMarray: V):IPersonView<T, U, V> => {
         let result: IPersonView<T, U, V> = {
             name, age, sex, isMarray
         }
         return result;
    }
Copy the code

As you can see, using generic interfaces is a friendly way to solve these problems. Easy to maintain and unify.

Two: common operators

(1): extends

It simply means to inherit, to have a type variable inherit from the type we’ve defined. Play a big role in generic constraints. (More on generic constraints later)

(2): keyof

This operator can be used to get all keys of a type whose return type is the union type. An 🌰 :

    interface Person {
        name: string;
        age: number;
        isMarray: boolean;
    }
    type per1 = keyof Person   // string | number | boolean
Copy the code

Three: generic constraints

Sometimes we may want to limit the number of types accepted per type variable, which is where generic constraints come in. Acts as a constraint on type variables.

For example 🌰 : When working with strings or arrays, we assume that the length attribute is available. When we use a function and try to print the length of the argument, some problems arise.

    const indentify5 = <T>(args: T):T= > {
        console.log(args.length);   Attribute 'length' not found on // // type 'T'
        return args;
    }
Copy the code

The reason for this problem is that because the TS compiler does not know, it cannot recognize whether or not a property is present on T. We can define a type and make the type variable extends.

    interface ILength {
        length: number   // Define an interface that contains length
    }
    
    const indentify6 = <T extends ILength>(args: T):T => {
        console.log(args.length);  // number
        return args;
    }
Copy the code

Of course: we can also define array types to solve this problem.

    const indentify7 = <T>(args: T[]): T[] => {
        console.log(args.length);
        return args;
    }
    
    const indentify8 = <T>(args: Array<T>): Array<T> => {
        console.log(args.length);
        return args;
    }
Copy the code

We can also use keyof to determine whether a key exists on an object. An 🌰 :

Declare a type interface. interface Ip {name: string,
        sex: string,
        age: number
    }
    // With the keyof operator, we can get all the keys of the specified type, and then we can combine the extends constraint
    // That is, the attribute name of the restricted input is contained in the union type returned by keyof
    
    const indentify9 = 
      
       (obj: Ip , key: K): Ip[k] { return obj[key]; }
      ,>Copy the code

Generic tools

(1): Partial

Make all attributes of a type non-required.

    type Partial<T> = {
        [P inkeyof T]? : T[P];/ /! To become?
    };
Copy the code

An 🌰 :

    interface IPerson {
        name: string,
        age: number } Partial<IPerson> === interface IPerson { name? : string |undefined.age: number | undefined
                          }
Copy the code

(2): Exclude

Remove certain types of T that belong to U

     type Exclude<T, U> = T extends U ? never : T; 
Copy the code
    type T = Exclude<"a" | "b" | "c"."a">   // "b" | "c"
Copy the code

(3): ReturnType

ReturnType is used to get the ReturnType of function T.

type T1 = ReturnType<() = > String>  // string
type T2 = ReturnType<(args: String) = > void>  // void
type T3 = ReturnType<<T>() = > T>; / / {}
Copy the code

The above is some simple use, welcome to point out.