关于TS中如何处理高阶函数

业务场景

当我们封装一个 ajax 请求,其本质是异步 Promise 的调用。

我们可以写一个高阶函数,在 Promise 的 executor 前展示 loading 提示,在请求结束后关闭 loading 提示。

这样,我们会得到如下requestLoadingWrapper的实现。

JS 实现

ajaxFunc(示例)

1
2
3
4
export const getUserInfo = async () =>
(await request) <
GetUserInfoType >
`${API_HGAME}/commission/cloud/getUserInfo`

requestLoadingWrapper

1
2
3
4
5
function requestLoadingWrapper (fn) {
Toast.loading()
const res = await fn()
Toast.hide()
}

以上,看似很简单,但是如果我们对 TS 的基础不太熟悉或者不够深入的话,写起来还是无从下手的。这样,我们来重温下 TS 的一些基础

重拾 TS 基础

条件类型 - extends 关键字

extends 用于条件判断中,其作用类似为 js 的 typeof 关键字。

注意:extends 运用在 type 和 class 中时完全是两种作用的效果

直接看例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type TypeName<T> = T extends string
? 'string'
: T extends number
? 'number'
: T extends boolean
? 'boolean'
: T extends undefined
? 'undefined'
: T extends Function
? 'function'
: 'object'

type T0 = TypeName<string> // "string"
type T1 = TypeName<'a'> // "string"
type T2 = TypeName<true> // "boolean"
type T3 = TypeName<() => void> // "function"
type T4 = TypeName<string[]> // "object"

有趣的实现:PowerPartial

使用递归,将 object 每一层的参数都转化为可选。

1
2
3
4
type PowerPartial<T> = {
// 如果是 object,则递归类型
[U in keyof T]?: T[U] extends object ? PowerPartial<T[U]> : T[U]
}

infer 关键字

用法:在条件类型语句中, 可以用 infer 声明一个类型变量并且对它进行使用。

  • 这里顺便就可以重温下 TS 中ReturnType 的实现,

ReturnType ,意思是获取 Function 类型的返回值类型。

1
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any
  • 类似地, Parameter 的实现也大同小异
1
2
3
4
5
6
// node_modules/typescript/lib/lib.es5.d.ts
type Parameters<T extends (...args: any[]) => any> = T extends (
...args: infer P
) => any
? P
: never
  • 还有 ConstructorParameter ,获取构造函数的参数
1
2
3
4
// node_modules/typescript/lib/lib.es5.d.ts
type ConstructorParameters<
T extends new (...args: any[]) => any
> = T extends new (...args: infer P) => any ? P : never
  • 还有 InstanceType ,获取构造函数的实例类型
1
2
3
4
5
6
// node_modules/typescript/lib/lib.es5.d.ts
type InstanceType<T extends new (...args: any[]) => any> = T extends new (
...args: any[]
) => infer R
? R
: any

TS 实现

获取 Promise 的返回值类型

利用条件类型和 infer 关键字,我们还可以方便地实现获取 Promise 对象的返回值类型

1
type Unpacked<T> = T extends Promise<infer P> ? P : T

提取传入函数的参数和返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export function requestLoadingWrapper<T extends (...args: any[]) => any>(
fn: T
) {
return async function (
...args: Parameters<T>
): Promise<Unpacked<ReturnType<T>>> {
Toast.loading('')
const res = await fn(...args)
Toast.hide()
if (!res || res.err) {
Toast.fail('获取失败')
}
return res
}
}

使用

1
2
3
4
5
6
7
export const getUserInfo = async () =>
await request<GetUserInfoType>(`${API_HGAME}/commission/cloud/getUserInfo`)
// ==> Promise => ({ ...xxxx })

// 包裹高阶函数后,类型正常传递
const request = requestLoadingWrapper(getUserInfo)
// ==> Promise => ({ ...xxxx })

参考