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

Typescript
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.

problem

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.

Code

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 :

unpaginate
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:

getParams
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;
hasMore
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;
dataAdaptor
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

Typescript
Has the type function been implemented at the time? Is it type safe? Will there be code hints when coding? Still say so
any
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

any
), but it s better than nothing, at least for
api
with
config
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,

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

Speaking of methods, in Typescript, you can use

Parameters
,
ReturnType
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

api
Type is not fixed, the need to dynamically in
api
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,
extends
Limits the lower limit of this generic: it must be a method and return a Promise result.

And then

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

api
Is generic,
Config
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
parmas
parameter. Can be considered here
P
It s just a random variable name and replace it with
T
it is also fine.

Combine

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

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

Parameters<T>[0]
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

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

But there will be a thorny problem here,

api
The type of the returned result is
Promsie<R>
, And the result returned by config should go
Promise
Of
R
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

extends
Clause used to tell
Typescript
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

2.generic

U
Is dynamic from
UnPromise<ReturnType<T>>
Derive it, and then pass it to
Config
The type transmission of the returned result is completed.

The third generic type-formatted result type

The last issue left to deal with is

dataAdaptor
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
unpaginate
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
Config.dataAdaptor
The return result,
dataAdaptor: (res: R) => V[]
In this way, Typescript can be based on specific scenarios
dataAdaptor
Returned array type => deduced
V
The type of it.

Then

V[]
As
unpaginate
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 .

Ending

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"