The generic
In languages like C# and Java, you can use generics to create reusable components that can support multiple types of data. This allows users to use components with their own data types.
/ / T: generic | type variables
function identity<T> (arg: T) :T {
return arg;
}
identity(123)
identity('123')
Copy the code
T stands for “Type” in code and is usually used as the first Type variable name when defining generics. But actually T can be replaced by any valid name.
Since we’re dealing with arrays, the.length property should exist. We can create this array just like any other array:
function loggingIdentity<T> (arg: T[]) :T[] {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
Copy the code
Instead of defining just one type variable, we can introduce as many type variables as we want. For example, we introduce a new type variable U that extends our defined identity function:
function identity<T.U> (value: T, message: U) :T {
console.log(message);
return value;
}
console.log(identity(Awesome!."jack is cool!"));
Copy the code
Generic type && Generic interface
Different annotation methods for generic types:
- The way function generics are annotated
- Object literals to define generic types
- How a generic interface is defined
// Different ways of generic types
function identity<T> (arg: T) :T {
return arg;
}
// 1. Function generic annotation:
let a: <T>(arg: T) = > T = identity
// 2. Define generic types in terms of object literals
let b: { <T>(arg: T): T } = identity
// 3. How to define a generic interface
interface IdentityInterface {
<T>(arg: T): T
}
let c: IdentityInterface = identity
Copy the code
Generic class & generic constraints
A generic class
A generic class looks like a generic interface. We just need to use <T,… > defines any number of variables of any type, as shown in the following example:
/ / a generic class
class MinClass<T>{
public list: T[] = []
add(num: T) {
this.list.push(num)
}
min(): T {
let minNum = this.list[0]
for (let i = 0; i < this.list.length; i++) {
if (minNum > this.list[i]) {
minNum = this.list[i]
}
}
return minNum
}
}
Copy the code
When do we need to use generics? There are usually two criteria for deciding whether to use generics:
- 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.
Generic constraint
Sometimes we might want to limit the number of types each type variable accepts, and that’s where generic constraints come in.
Take an official document as an example:
We need to define an interface to describe the constraint.
Create an interface that contains the.length property and use the extends keyword to implement the constraint:
interface LengthInterface {
length: number
}
function loggingIdentity<T extends LengthInterface> (arg: T) :T {
console.log(arg.length);
return arg;
}
Copy the code
The generic constraints are: extends extends an interface (not necessarily an interface), and T extends LengthInterface is used to tell the compiler that we support any type that has implemented the Length interface
In addition, a generic constraint does not have to be an interface. For example, we can replace the above code interface with a type alias, as shown in the following example
type LengthType = string
function loggingIdentity<T extends LengthType> (arg: T) :T {
console.log(arg.length);
return arg;
}
Copy the code
Keyof operator
The keyof operator can be used to get all the keys of a type whose return type is the union type.
// The keyof operator
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
You can see that k1,k2, and K3 are actually key names
Use type parameters in generic constraints
Once we understand the keyof operator, the following code should make sense
function getProperty<T.K extends keyof T> (obj: T, key: K) {
return obj[key];
}
let x = { a: 1.b: 2.c: 3.d: 4 };
getProperty(x, "a"); // okay
getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.
Copy the code
** and K is ‘a’, ‘b’, ‘c’, ‘d’ **
It is clear that by using generic constraints, errors can be detected early at compile time, greatly improving the robustness and stability of the program.
Multiple generic constraints & cross types
Crossover typing is the merging of multiple types into a single type. This allows us to add existing types together into a single type that contains all the required features of the type.
function extend<T.U> (first: T, second: U) :T & U {
letresult = <T & U>{}; For (let id in first) {(<any>result)[id] = (<any>first)[id]; } for (let id in second) {if (! result.hasOwnProperty(id)) { (<any>result)[id] = (<any>second)[id]; }} return result; // Return result; // Return result; } class Person {constructor(public name: constructor);} class Person {constructor(public name: constructor); string) { } } interface Loggable { log(): void; } class ConsoleLogger implements Loggable { log() { // ... } } var jim = extend(new Person("Jim"), new ConsoleLogger()); var n = jim.name; jim.log();Copy the code
Here’s another simple example:
interface Sentence {
content: string
title: string
}
interface Music {
url: string
}
class Test<T extends Sentence & Music>{
props: T
constructor(public arg: T) {
this.props = arg
}
info() {
return {
// The reason we can use this.props. XXX is because arg is of type T, and T inherits the properties of both interfaces.
content: this.props.content,
title: this.props.title,
url: this.props.url
}
}
}
Copy the code
Class types in generics
Class types in generics are intended to be constraints or better inferences
class BeeKeeper {
hasMask: boolean;
}
class ZooKeeper {
nametag: string;
}
class Animal {
numLegs: number;
}
class Bee extends Animal {
keeper: BeeKeeper;
}
class Lion extends Animal {
keeper: ZooKeeper;
}
function createInstance<A extends Animal> (c: new () => A) :A {
return new c();
}
createInstance(Lion).keeper.nametag; // typechecks!
createInstance(Bee).keeper.hasMask; // typechecks!
Copy the code