This is the second day of my participation in the November Gwen Challenge. Check out the details: the last Gwen Challenge 2021
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.
The genericHello World
If we want to define a function identity, the arg is of type string and the return value is arg itself. Without generics, the function would look like this:
function identity(arg: string) :string {
return arg;
}
Copy the code
To support any type of parameter, we can use the type variable T, which is a special variable that only represents the type, not the value.
function identity<T> (arg: T) :T {
return arg;
}
Copy the code
We call this version of the Identity function generic because it can be applied to multiple types.
Once we have defined a generic function, we can use it in two ways.
The first method passes all the arguments, including the type arguments:
let output = identity<string> ("Hello World"); // type of output will be 'Hello World'
Copy the code
The second approach takes advantage of type corollary — that is, the compiler automatically helps us determine the type of T based on the parameters passed in. Type inference helps us keep our code lean and readable.
let output = identity("Hello World"); // type of output will be 'Hello World'
Copy the code
Function arguments can be arrays of type variables T[]. The code is as follows:
function loggingIdentity<T> (arg: T[]) :T[] {
console.log(arg.length);
return arg;
}
Copy the code
We can also implement the above example with Array
:
function loggingIdentity<T> (arg: Array<T>) :Array<T> {
console.log(arg.length);
return arg;
}
Copy the code
The generic type
The type of a generic function is no different from that of a non-generic function, except that there is a type parameter T first, like a function declaration:
function identity<T> (arg: T) :T {
return arg;
}
let myIdentity: <T>(arg: T) = > T = identity;
console.log(typeof myIdentity) // "function"
Copy the code
We can also use different generic parameter names, as long as they match in number and usage.
function identity<T> (arg: T) :T {
return arg;
}
let myIdentity: <U>(arg: U) = > U = identity;
console.log(typeof myIdentity) // "function"
Copy the code
Use more than one generic parameter:
function identity<T.L> (name: T, len: L) :T.L] {
return [name, len];
}
Copy the code
We can also define generic functions using object literals with call signatures:
function identity<T> (arg: T) :T {
return arg;
}
let myIdentity: {<T>(arg: T): T} = identity;
Copy the code
This led us to write our first generic interface.
A generic interface
Let’s take the object literal from the above example as an interface:
function identity<T> (arg: T) :T {
return arg;
}
interface GenericIdentityFn {
<T>(arg: T): T;
}
let myIdentity: GenericIdentityFn = identity;
Copy the code
We might want to treat the generic parameter T as a parameter to the entire interface, so that other members of the interface can know the type of the parameter.
function identity<T> (arg: T) :T {
return arg;
}
interface GenericIdentityFn<T> {
(arg: T): T;
}
let myIdentity: GenericIdentityFn<number> = identity;
Copy the code
A generic class
In addition to generic interfaces, we can also create generic classes. Generic classes look much like generic interfaces. Generic classes use (<>) to enclose generic types after the class name.
A class has two parts: a static part and an instance part. A generic class refers to the type of the instance part, so static attributes of the class cannot use this generic type.
Note that generic enumerations and generic namespaces cannot be created.
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) = > T;
}
let myGenericNumber = new GenericNumber<number> (); myGenericNumber.zeroValue =0;
myGenericNumber.add = function(x, y) { return x + y; };
let stringNumeric = new GenericNumber<string> (); stringNumeric.zeroValue ="";
stringNumeric.add = function(x, y) { return x + y; };
console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));
Copy the code
The GenericNumber generic class is not limited to the number type, but can also use strings or other more complex types.
Generic constraint
Make sure properties exist
We define an interface to describe the constraints. Create an interface that contains the.length attribute and use this interface and the extends keyword to implement the constraint:
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise> (arg: T) :T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
Copy the code
Now the generic function is constrained, so it is no longer applicable to any type. We need to pass a value that matches the constraint type and must contain the required length attribute:
loggingIdentity(3); // Error, number doesn't have a .length property
loggingIdentity({length: 10.value: 3});
Copy the code
Checks whether a key exists on an object
You can declare one type parameter and it is bound by another type parameter.
For example, now we want to get the property from the object using the property name, and we want to make sure the property exists on the object.
function getProperty<T.K extends keyof T> (obj: T, key: K) :T[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
A generic class
To use generics in a class, we simply add multiple type variables
.
interface MyInt<U> {
value: U
getValue: () = > U
}
class MyClass<T> implements MyInt<T> {
value: T
constructor(value: T) {
this.value = value
}
getValue(): T {
return this.value
}
}
const myNumberClass = new MyClass<number> (111);
console.log(myNumberClass.getValue()); / / 111
const myStringClass = new MyClass<string> ("hhh");
console.log(myStringClass.getValue()); // hhh
Copy the code
Use generics to create objects
Construct the signature
In TypeScript interfaces, you can use the new keyword to describe a constructor:
interface Point {
new (x: number.y: number): Point;
}
Copy the code
The new (x: number, y: number) in the above interface is called the construction signature, and its syntax is as follows:
ConstructSignature: newTypeParametersopt (ParameterListopt) TypeAnnotationopt// TypeParametersopt, ParameterListopt, and TypeAnnotationopt represent optional type parameters, an optional parameter list, and optional type annotations, respectively
Copy the code
Constructor type
In the TypeScript language specification, constructor types are defined as:
Object types that contain one or more construction signatures are called constructor types;
Constructor types can be written using constructor type literals or object type literals that contain construction signatures.
So what is a constructor type literal? Constructor type literals are shorthand for object types that contain the signature of a single constructor. In particular, constructor type literals take the form:
new < T1, T2, ... > ( p1, p2, ... ) = > R
Copy the code
This form is equivalent to the following object type literals:
{ new < T1, T2, ... > ( p1, p2, ... ) : R }
Copy the code
Example 1:
Constructor type literals:
// Constructor type literals
new (x: number.y: number) => Point
Copy the code
Equivalent to the following object type literals:
// Object type literals
{
new (x: number.y: number): Point;
}
Copy the code
Example 2:
interface Point {
x: number;
y: number;
}
interface PointConstructor { // Constructor type
new (x: number.y: number): Point;
}
// Class implements the interface
class Point2D implements Point {
readonly x: number;
readonly y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y; }}// Define the function newPoint
function newPoint(pointConstructor: PointConstructor, x: number, y: number) :Point {
return new pointConstructor(x, y);
}
const point: Point = newPoint(Point2D, 2.2);
console.log(point);
Copy the code
Use generics to create objects
Sometimes, a generic class may need to create its type-related objects based on the passed generic T. Such as:
class FirstClass { id? :number;
}
class SecondClass {
name: string;
}
class GenericCreator<T> {
create<T>(c: { new (): T }): T {
return newc(); }}const creator1 = new GenericCreator<FirstClass>();
const firstClass: FirstClass = creator1.create(FirstClass);
firstClass.id = 1;
const creator2 = new GenericCreator<SecondClass>();
const secondClass: SecondClass = creator2.create(SecondClass);
console.log(firstClass, secondClass)
Copy the code
In the code above, we defined two normal classes and a generic class GenericCreator
. In the generic GenericCreator generic class, we define a member method called create that uses the new keyword to call the constructor of the actual type passed in to create the corresponding object.
After writing the above code, SOMETIMES Typescript is too cumbersome… = =!
reference
-
Generic · TypeScript Chinese Language
-
Learn about TypeScript generics and applications
-
TypeScript generics you don’t know about