TypeScript Generics
Generics is a programming language feature that allows placeholders to be used to represent types instead of concrete types when defining functions, classes, interfaces, and so on.
Generics is a very useful feature when writing reusable, flexible, and type-safe code.
The main purpose of using generics is to handle data of unspecified types, allowing code to apply to multiple data types without losing type checking.
The advantages of generics include:
Code reuse:You can write generic code independent of specific types, improving code reusability.
Type safety:Type checking is performed at compile time, avoiding type errors at runtime.
Abstraction:Allows writing more abstract and generic code that adapts to different data types and data structures.
Generic Identifiers
In generics, some conventional identifiers are commonly used, such as the commonT(representing Type),U、Vetc. But in fact, you can use any identifier.
T: Represents "Type", and is the most common generic type parameter name.
function identity<T>(arg: T): T {
return arg;
}K, V: Used to represent generic type parameters for keys (Key) and values (Value).
interface KeyValuePair<K, V> {
key: K;
value: V;
}E: Used to represent the generic type parameter for array elements.
function printArray<E>(arr: E[]): void {
arr.forEach(item => console.log(item));
}R: Used to represent the generic type parameter for function return values.
function getResult<R>(value: R): R {
return value;
}U, V: Usually used to represent the second and third generic type parameters.
function combine<U, V>(first: U, second: V): string {
return `${first} ${second}`;
}
These identifiers are conventional. In fact, you can choose any names that conform to identifier rules. The key is to make the code readable and easy to understand, so it is recommended to use descriptive names for generic type parameters to help understand their purpose.
Generic Functions
Use generics to create a function that can handle different types:
Example
return arg;
}
// Using a generic function
let result = identity<string>("Hello");
console.log(result); // Output: Hello
let numberResult = identity<number>(42);
console.log(numberResult); // Output: 42
Analysis:In the above example,identityis a generic function, using<T>to represent the generic type. It accepts a parameterargand the return value are both of the generic typeT. When using it, you can explicitly specify the generic type via angle brackets<>. The first call specifiesstringtype, and the second call specifiesnumbertype.
2. Generic Interfaces
You can use generics to define interfaces, allowing members of the interface to use any type:
Example
interface Pair<T, U> {
first: T;
second: U;
}
// Using a generic interface
let pair: Pair<string, number> = { first: "hello", second: 42 };
console.log(pair); // Output: { first: 'hello', second: 42 }
Analysis:Here a generic interface is definedPair, which has two type parametersTandU. Then, an object was created using this generic interfacepair, wherefirstis a string type,secondis a number type.
3. Generic Classes
Generics can also be applied to instance variables and methods of a class:Example
class Box<T> {
private value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
}
// Using a generic class
let stringBox = new Box<string>("TypeScript");
console.log(stringBox.getValue()); // Output: TypeScript
Analysis:In this example,Boxis a generic class, using<T>to represent the generic type. Both the constructor and methods can use the generic typeT. By instantiatingBox<string>, we created aBoxinstance that stores a string, and through thegetValuemethod, we obtained the stored value.
4. Generic Constraints
Sometimes you want to restrict the type scope of generics; you can use generic constraints:
Example
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(arg: T): void {
console.log(arg.length);
}
// Correct usage
logLength("hello"); // Output: 5
// Incorrect usage, because numbers do not have a length property
logLength(42); // Error
Analysis:In this example, a generic function is definedlogLength, which accepts an argument of typeT, but there is a constraint condition, namelyTmust implement theLengthwiseinterface, which requires alengthproperty. Therefore, it is correct to calllogLength("hello"), but it is not allowed to calllogLength(42), because numbers do not have alengthproperty.
5. Generics and Default Values
You can set default values for generics, so that when no type parameter is specified, the default type can be used:
Example
function defaultValue<T = string>(arg: T): T {
return arg;
}
// Using a generic function with default values
let result1 = defaultValue("hello"); // Inferred as string type
let result2 = defaultValue(42); // Inferred as number type
Explanation:This example demonstrates a generic function with default values. The functiondefaultValueaccepts a generic parameterT, and sets its default type tostring. When using it, if no type is explicitly specified, the default type will be used. In the example, in the first callresult1is inferred asstringtype, and in the second callresult2is inferred asnumbertype.