Typescript generic package teaching package meeting

Typescript generic package teaching package meeting

I don t know if there is such a scene in your daily work: Obviously

I have read the official document many times, but I actually wrote the code but suffered various difficulties. I encountered an error, and after searching to no avail, I had no choice but to write any. (I guess yes, otherwise you won't click on this article.

What prevents you from getting closer to strong typing is in most cases because generics have not yet been fully mastered. This article will start with an example I encountered in my daily work, and introduce step by step where to use generics and how to write~

(What if you are not familiar with other knowledge points of Typescript in addition to generics? ? You can learn Typescript with examples from another comprehensive article I compiled earlier .

Let's begin.


In other words, the backend provides multiple interfaces that support paging to check list data, and the parameter formats, response results, and paging forms of these interfaces may be different. Take the form of pagination, there are several common types of pagination parameters, such as the number of pages and the number of pages per page, the offset value and limit, and the last id of the previous page to query.

{ page_size : number, page_num : number } { offset : number, limit : number } { forward : boolean last_id : string page_size : number } ... Copy code

The data volume of these interfaces is about several thousand pieces of data. Considering the pressure of the database, back-end students do not recommend pulling several thousand pieces of data at a time. The front-end paging is required to pull all of them.

In order to avoid the paging logic to be written once for each interface, it is required to implement a strongly typed tool method to realize the function of automatically paging to pull all data.


The focus of this article is not on how to implement such a function. Simply draw a flowchart. I believe most people can achieve it.

A feasible code implementation is as follows:

const unpaginate = ( api, config, ) => { const {getParams, hasMore, dataAdaptor} = config async function iterator ( time, lastRes ) { //Get the parameters of the next request through the results of the previous request and the number of requests const params = getParams(lastRes, time) const res = await api(params) let next = [] //If there is a next page, continue to pull if (hasMore(res, params)) { next = await iterator(time + 1 , res) } //The splicing results are returned together return dataAdaptor(res).concat(next) } return iterator() } Copy code

Code interpretation :

The first parameter of the method is passed in an api method that returns the Promise result; the second parameter supports the passing of a configurable object:

The method will return the result of the last request and the current number of requests, which is convenient for the user to set the request parameters;
The method will return the results and parameters of the current request, and the user needs to inform the program whether the pull has been completed;
The method returns the result of each request to allow customizing the format of the returned result (for example, changing the underscore of a field to camel case), and save the returned value as the final result;

Think about it, you are using

Has the type function been implemented at the time? Is it type safe? Will there be code hints when coding? Still say so
Where's a shuttle?

Next, we will provide type support for this method step by step .

Typescritp generic blessing

Start with parameters and write the most basic type declarations for api and config .

export interface Config { hasMore : ( res?: any, params?: any ) => boolean getParams : ( res?: any, time?: number ) => any dataAdaptor : ( res: any ) => any[] } const unpaginate = ( api: ( params: any ) => Promise <any[]>, config: Config, ): Promise <any[]> => { ... } Copy code

The above type declaration is not very useful (because there are

), but it s better than nothing, at least for
An error will be reported when passing a parameter that does not match the type.

The first generic-parameter type

It's easy to see,

The parameters of the method in the type and
Type strong association .
The type of parameter determines
Method of
Parameter Type. As for the type of the returned result, all three methods are used.

Speaking of methods, in Typescript, you can use

To extract the parameter type and return value type from the method type.

type EventListenerParamsType = Parameters< typeof window .addEventListener>; //[type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions | undefined] type A = ( a: number ) => string type B = ReturnType<A> //string copy code

And here

Type is not fixed, the need to dynamically in
The type is extracted from the type, and the generic type appears .

const unpaginate = <T extends (params: any) => Promise<any>>( api: T, config: Config, ): Promise<any[]> => { ... } Copy code

We added before the method

<T extends (params: any) => Promise<any>>
This piece of code means that a generic is declared,
Limits the lower limit of this generic: it must be a method and return a Promise result.

And then

Type assignment
, And then use the type after writing
, Typescript dynamically calls according to the actual
The method type is automatically deduced .

Is generic,
Of course, it also needs to be generic, which can be passed as a parameter .

export interface Config<P> { hasMore : ( res?: R, params?: P ) => boolean //... } Copy code

interface Config<P>
Here we let Config also support generic parameters and pass them to
parameter. Can be considered here
It s just a random variable name and replace it with
it is also fine.


Generic tool method, take
The first parameter type is passed to
, So their types are related.

const unpaginate = <T extends (params: any) => Promise<any>>( api: T, config: Config<Parameters<T>[0]>, ): Promise<any[]> => { ... } Copy code

Means, take the first parameter type of a parameter of type T (which is an array type).

The second generic type-the type of the return value

The parameter type can be derived dynamically, according to reason

The return result of can also be achieved using the same operation.

But there will be a thorny problem here,

The type of the returned result is
, And the result returned by config should go
Types of.

To extract types from generics, we will use

, Look at the code directly:

type UnPromise<T> = T extends Promise <infer U>? U: undefined type A = Promise <number> type B = UnPromise<A> //number copy code

If generic is a dynamic type, infer is a dynamic dynamic type . In the above example, we are

Clause used to tell
The type here needs to be derived dynamically.

Extract the entity type of the return value, continue to improve the type definition:

export interface Config<P, R> { hasMore : ( res?: R, params?: P ) => boolean getParams : ( res?: R, time?: number ) => Partial<P> dataAdaptor: ( res: R ) => any[] } type UnPromise<T> = T extends Promise <infer U>? U: undefined const unpaginate = < T extends (params: any) => Promise <any>, U extends UnPromise<ReturnType<T>> >( api: T, config : Config<Parameters<T>[ 0 ], U>, ): Promise <any[]> => { ... } Copy code


Is dynamic from
Derive it, and then pass it to
The type transmission of the returned result is completed.

The third generic type-formatted result type

The last issue left to deal with is

The result type of the return value. We don't have any restrictions on the results it returns, all we need to do is let Typescirpt derive and pass it by itself. And as
The return result type of the method.

Here you need to define another generic:

export interface Config<P, R, V> { //... dataAdaptor : ( res: R ) => V[] } const unpaginate = < T extends (params: any) => Promise <any>, U extends UnPromise<ReturnType<T>>, V extends any >( api: T, config : Config<Parameters<T>[ 0 ], U, V>, ): Promise <V[]> Copy code

We use

V extends any
Defines a new generic type and passes it to
The return result,
dataAdaptor: (res: R) => V[]
In this way, Typescript can be based on specific scenarios
Returned array type => deduced
The type of it.


The return value type, so that it can be stringed together.

final effect

API method parameter deduction:

API method returns result deduction:

Deduction of the result after formatting:

You can experience it on the Typescript playground , and the code can also be found on my github .


This article introduces step by step how to use generics to implement type declarations for a common method. I hope it will be helpful to you after reading it. Students who are not familiar with Typescript can read another article I wrote before, "Learning Typescript with Examples"