What is a generic

In languages like C# and Java, generics can be used to create reusable components that can support multiple types of data. This allows users to use components with their own data types

The key purpose of generics is to provide meaningful constraints between members: instance members of the class, methods of the class, function parameters, and function return values

Instead of defining only one type variable, we can introduce as many type variables as we wish. For example, we introduce a new type variable U to extend our identity function:

/* applies to the function */
function identity <T.U> (value: T, message: U) : T {
  console.log(message);
  return value;
}

console.log(identity<Number.string> (68."Semlinker"));
Copy the code

The compiler automatically selects the type

function identity <T.U> (value: T, message: U) : T {
  console.log(message);
  return value;
}

console.log(identity(68."Semlinker"));
Copy the code

Two generic interfaces

interface Person<T, N> {
    name: T;
    value: N
}
function identity<T.U> (value: T, message: U) :Identities<T.U> {
  console.log(value + ":" + typeof (value));
  console.log(message + ":" + typeof (message));
  let identities: Identities<T, U> = {
    value,
    message
  };
  return identities;
}

console.log(identity(68."Semlinker"));
Copy the code

Three generic class

Used after the class name to define any number of type variables

interface GenericInterface<U> {
  value: U
  getIdentity: () = > U
}

class IdentityClass<T> implements GenericInterface<T> {
  value: T

  constructor(value: T) {
    this.value = value
  }

  getIdentity(): T {
    return this.value
  }

}

const myNumberClass = new IdentityClass<Number> (68);
console.log(myNumberClass.getIdentity()); / / 68

const myStringClass = new IdentityClass<string> ("Semlinker!");
console.log(myStringClass.getIdentity()); // Semlinker!
Copy the code

When exactly do you use generic classes?

  1. When your function, interface, or class will handle multiple data types;
  2. When a function, interface, or class uses the data type in more than one place.

Four generic constraint extends

T extends Length is used to tell the compiler that we support any type that already implements the Length interface

Later, TypeScript prompts an error message when we call the identity function with an object that does not contain the length attribute



/ * 1 * /
interface Length {
  length: number;
}

function identity<T extends Length> (arg: T) :T {
  console.log(arg.length); // Get the length attribute
  return arg;
}
/* 2 set to array type to solve the above problem */

function identity<T> (arg: T[]) :T[] {
   console.log(arg.length);  
   return arg; 
}

// or
function identity<T> (arg: Array<T>) :Array<T> {      
  console.log(arg.length);
  return arg; 
}
Copy the code

Checks whether a key exists on an object

The keyof operator, introduced in TypeScript 2.1, can be used to get all keys of a type whose return type is a union type

interface Person {
  name: string;
  age: number;
  location: string;
}

type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[];  // number | "length" | "push" | "concat" | ...
type K3 = keyof { [x: string]: Person };  // string | number
Copy the code

Use generics with Keyof

function myFun<T.K extends keyof T> (obj: T, key: K) :T[K] {
    return obj[key];
}
Copy the code

In the getProperty function above, we use K extends Keyof T to ensure that the parameter key must be a key contained in the object so that no runtime errors can occur. This is a type-safe solution, with a simple call to let value = obj[key]; different

The default type of the generic parameter

Specifies the default type for the type parameter in the generic

interface A<T=string> {
  name: T;
}

const strA: A = { name: "Semlinker" };
const numB: A<number> = { name: 101 };
Copy the code

T extends U? X : Y

Condition type, which checks the type relationship with a condition expression

The following code means:

The type is X if T can be assigned to U, and Y otherwise

In conditional type expressions, we also usually combine the infer keyword to achieve type extraction

T extends U ? X : Y
Copy the code

In the example above, we declare a type variable V using the infer keyword when type T satisfies the T extends Dictionary constraint, and return that type, otherwise return never.

Any cannot be assigned to never

interface Dictionary<T = any> {
  [key: string]: T;
}
 
type StrDict = Dictionary<string>

type DictMember<T> = T extends Dictionary<infer V> ? V : never
type StrDictMember = DictMember<StrDict> // string
Copy the code

Using the conditional type and the infer keyword, we can also easily implement the return value type of a Promise object

async function stringPromise() {
  return "Hello, Semlinker!";
}

interface Person {
  name: string;
  age: number;
}

async function personPromise() {
  return { name: "Semlinker".age: 30 } as Person;
}

type PromiseType<T> = (args: any[]) = > Promise<T>;
type UnPromisify<T> = T extends PromiseType<infer U> ? U : never;

type extractStringPromise = UnPromisify<typeof stringPromise>; // string
type extractPersonPromise = UnPromisify<typeof personPromise>; // Person
Copy the code

Generic tool types

Common tool types such as Partial, Required, Readonly, Record, and ReturnType are built into TypeScript for developers’ convenience

/* Partial */
type Partial<T> = {
    [P inkeyof T]? : T[P]; };/* Record sets the value of all attributes in K to T */
type Record<K extends keyof any, T> = {
    [P in K]: T;
};
interface PageInfo {
  title: string;
}

type Page = "home" | "about" | "contact";

const x: Record<Page, PageInfo> = {
  about: { title: "about" },
  contact: { title: "contact" },
  home: { title: "home"}};/* Pick Pick
      
        extends keyof T 
       
         * /
       ,>
      ,>
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
}
interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type TodoPreview = Pick<Todo, "title" | "completed">;

const todo: TodoPreview = {
  title: "Clean room".completed: false
};
/* Exclude Exclude
      
        removes a type that belongs to another type. * /
      ,>
// Never is returned if T can be assigned to U, T is returned otherwise.
// The final effect is to remove some of the U types from T
type Exclude<T, U> = T extends U ? never : T;
// demo 
type T0 = Exclude<"a" | "b" | "c"."a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c"."a" | "b">; // "c"
type T2 = Exclude<string | number | (() = > void), Function>; // string | number
/* ReturnType is used to get the ReturnType of T */
type ReturnType<T extends(... args:any) = >any> = T extends(... args:any) => infer U ? U : any;
// demo

type T0 = ReturnType<() = > string>; // string
type T1 = ReturnType<(s: string) = > void>; // void
type T2 = ReturnType<<T>() = > T>; / / {}
type T3 = ReturnType<<T extends U, U extends number[] >() = > T>; // number[]
type T4 = ReturnType<any>; // any
type T5 = ReturnType<never>; // any
type T6 = ReturnType<string>; // Error
type T7 = ReturnType<Function>; // Error
Copy the code

Create objects using generics

Construct the signature

interface Point {
  new (x: number.y: number): Point;
}

class Point2D implements Point {
  readonly x: number;
  readonly y: number;

  constructor(x: number, y: number) {
    this.x = x;
    this.y = y; }}function newPoint(
  pointConstructor: PointConstructor,
  x: number,
  y: number
) :Point {
  return new pointConstructor(x, y);
}

const point: Point = newPoint(Point2D, 1.2);
Copy the code

Use generics to create objects

class GenericCreator<T> {
  create<T>(c: { new (): T }): T {
    return new c();
  }
}

create<T>(c: { new(a: number): T; }, num: number): T {
  return new c(num);
}

const creator1 = new GenericCreator<FirstClass>();
const firstClass: FirstClass = creator1.create(FirstClass);

const creator2 = new GenericCreator<SecondClass>();
const secondClass: SecondClass = creator2.create(SecondClass);
Copy the code

The resources

Learn about TypeScript generics and applications