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?
- When your function, interface, or class will handle multiple data types;
- 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