Generic of [Typescript Introduction Manual]
🚀 [TypeScript Introduction Manual] records the concept of Ts with high attendance, which is designed to help you understand and be familiar with Ts
🎉 This series will continue to be updated and corrected, focusing on the points you are interested in. Welcome to leave messages and exchange, and encourage each other on the way to advancement!
👍 star this project gives the author some encouragement
📚 Series of articles, collection is not lost
1, Generics
An important part of software engineering is to build components. Components need not only well-defined and consistent API s, but also reusable. Good components are not only compatible with today's data types, but also applicable to data types that may appear in the future. We can create a component that supports many types, so that users can consume these components with their own types.
Let's start with the first generic, an identity function. The so-called identity function is a function that returns any incoming content. You can also understand it as similar to the echo command.
function identity(arg: number): number { sreturn arg; }
We need a way to capture the parameter type and then use it to represent the type of return value. Here we use a type variable, a special variable used for types rather than values.
function identity<Type>(arg: Type): Type { return arg; }
Now we have added a Type variable Type to the identity function. This Type allows us to capture the Type provided by the user, so that we can use this Type next. Here, we use Type again as the Type of the returned value. In the current writing method, we can clearly know that the Type of parameter and return value are the same.
After we write a generic identity function, we have two ways to call it. The first method is to pass in all parameters, including type parameters:
let output = identity<string>("myString");
The second method may be more common. Here we use type argument inference. We hope that the compiler can automatically infer and set the value of Type based on the parameters we pass in.
let strA = identity('isString'); // let strA: string
Note that this time we did not use the < Type > explicit incoming Type. When the compiler sees the value of isString, it will automatically set the Type to its Type (i.e. string).
Type parameter inference is a very useful tool, which can make our code shorter and easier to read. In some more complex examples, when the compiler fails to infer the type, you need to explicitly pass in the parameters as in the previous example.
2, Working with Generic Type Variables
In the example we learned in the previous section, Type is passed directly as a Type, and in some cases, Type can also be a parameter of the Type, for example:
function loggingIdentity<Type>(arg: Type[]): Type[] { console.log(arg.length); console.log(arg[0]); return arg; }
You can understand the Type of loggingIdentity in this way: the generic function loggingIdentity accepts a Type parameter and an argument arg, which is an array of Type. This function returns an array of Type.
Now we use the Type variable Type as part of the Type we use, rather than the whole Type before, which will give us more freedom.
3, Generic types
The form of a generic function is the same as that of other non generic functions. You need to list a type parameter first, which is a bit like a function declaration:
function identity<Type>(arg: Type): Type { console.log('>>:', arg); return arg; } let myIdentity: <T>(arg: T) => T = identity;
Note: don't be confused about < T >, generic type parameters (type | t | XX) can use different names, as long as the quantity and usage are consistent.
We can also write this generic type in the form of call signature of object type:
function identity<Type>(arg: Type): Type { return arg; } let myIdentity: { <Type>(arg: Type): Type } = identity;
This can lead us to write the first generic interface. Let's use the object literal in the previous example, and then move its code into the interface:
interface GenericIdentityFn { <Type>(arg: Type): Type; } function identity<Type>(arg: Type): Type { return arg; } let myIdentity: GenericIdentityFn = identity;
4, Generic Classes
Generic classes are written similarly to generic interfaces. After the class name, wrap the type parameter list with < > in angle brackets:
class GenericNumber<NumType> { zeroValue: NumType; add: (x: NumType, y: NumType) => NumType; } let myGenericNumber = new GenericNumber<number>(); myGenericNumber.zeroValue = 0; myGenericNumber.add = function(x, y) { return x + y; };
In this example, there is no restriction that you can only use the number type. We can also use other types:
type strOrNum = string | number; class GenericNumber<NumType> { zeroValue: NumType; log: (x: NumType, y: NumType) => NumType; } let myGenericNumber = new GenericNumber<strOrNum>(); myGenericNumber.zeroValue = 0; myGenericNumber.log = function(x, y) { console.log(x, y); return `${x}and ${y}`; // (parameter) x: strOrNum // (parameter) y: strOrNum };
Just like interfaces, putting type parameters on a class ensures that all properties in the class use the same type.
5, Generic Constraints
At an earlier time Chapter 3 functions We want to get the parameter obj Length attribute, but the compiler cannot prove that every type has The length attribute, rather than being compatible with any type, we prefer to constrain this function so that it can use with The type of the length property. As long as the type has this member, we are allowed to use it, but there must be at least this member.
function longest<Type extends { length: number }>(a: Type, b: Type) { if (a.length >= b.length) { return a; } else { return b; } }
We can also list the necessary conditions in the Type constraint. Therefore, we need to create an interface to describe the constraint. Here, we have created a system with only The interface of the length attribute, and then we use this interface and the extend keyword to implement the constraint:
interface Lengthwise { length: number; } function longest<Type extends Lengthwise>(a: Type, b: Type) { if (a.length >= b.length) { return a; } else { return b; } }
5.1 Using Type Parameters in Generic Constraints
You can declare a type parameter that is constrained by other type parameters.
For example, we want to get the named value. An object is given an attribute. Therefore, we need to ensure that we do not get attributes that do not exist on obj. So we establish a constraint between the two types:
function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) { return obj[key]; } let x = { a: 1, b: 2, c: 3, d: 4 }; getProperty(x, "a"); getProperty(x, "m"); // Parameters of type'm 'cannot be assigned to parameters of type' a '| B' | C '| d'.
5.2 Using Class Types in Generics
In TypeScript, when using factory mode to create instances, it is necessary to infer the type of class through their constructor. For example:
function create<Type>(c: { new (): Type }): Type { return new c(); } class BeeKeeper { hasMask: boolean = true; } class ZooKeeper { nametag: string = "Mikle"; } class Animal { numLegs: number = 4; } class Bee extends Animal { keeper: BeeKeeper = new BeeKeeper(); } class Lion extends Animal { keeper: ZooKeeper = new ZooKeeper(); } function createInstance<A extends Animal>(c: new () => A): A { return new c(); } createInstance(Lion).keeper.nametag; createInstance(Bee).keeper.hasMask;
Write at the end
This article is the fifth in the introduction to Typescript. In fact, this article needs more energy to "understand". You might as well read it several times and remember star
reference resources:
About me:
- Flower name: Yu Guang
- Email: webbj97@163.com
- csdn: Portal
Other precipitates:
- GitHub: solution to LeetCode in JS version
- Yu Guang's front-end growth notes
- High frequency manual tearing code series