I don t know if there is such a scene in your daily work: Obviously
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 :
Think about it, you are using
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
The first generic-parameter type
It's easy to see,
Speaking of methods, in Typescript, you can use
Parameters,ReturnTypeTo 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
const unpaginate = <T extends (params: any) => Promise<any>>(
api: T,
config: Config,
): Promise<any[]> => {
...
}
Copy code
We added before the method
And then
export interface Config<P> {
hasMore : ( res?: R, params?: P ) => boolean
//...
}
Copy code
Combine
const unpaginate = <T extends (params: any) => Promise<any>>(
api: T,
config: Config<Parameters<T>[0]>,
): Promise<any[]> => {
...
}
Copy code
The second generic type-the type of the return value
The parameter type can be derived dynamically, according to reason
But there will be a thorny problem here,
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
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
The third generic type-formatted result type
The last issue left to deal with is
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
Then
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"