TS泛型的一些应用

背景

最近用 TS 重写了部分工具函数,其中用到了泛型,在这里做一个分享,帮助大家快速熟悉泛型。

泛型

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

简单例子

首先,我们来实现一个函数 createArray,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值:

1
2
3
function createArray(length: number, value: any): Array<any> {
return Array(length).fill(value)
}

这段代码编译不会报错,但是一个显而易见的缺陷是,它并没有准确的定义返回值的类型。

使用泛型后,类型就可以传递了

1
2
3
function createArray<T>(length: number, value: T): Array<T> {
return Array(length).fill(value)
}

接着在调用的时候,可以指定它具体的类型为 string。当然,也可以不手动指定,而让类型推论自动推算出来。

实现一个 withStyles HOC

withStyles 的作用是将 CSS Modules 的 styles 对象注入原组件,以达到「样式参数化」的目的。并且可以通过不断叠加 hoc 实现样式的复用。这里先不讨论该 HOC 的使用场景,它就是一个平平无奇的 HOC,我们用它做例子看看泛型怎么使用。

1
const ComponentWithStyles = withStyles(styles)(Component)

使用泛型改造

withStyles.tsx

由于对传入的组件属性不可知,以下代码用 any 随便糊弄一顿后,我们会发现它跟我们用 JS 写没有什么区别,TS 的作用没有发挥出来。

1
2
3
4
5
6
7
8
const withStyles = (stylesProp: object) => (WrappedComponent: any) => {
const ComponentWithStyles = ({ styles, ...props }: React.FC<any>) => {
const comStyles = Object.assign({}, stylesProp, styles)
return <WrappedComponent styles={comStyles} {...props} />
}

return ComponentWithStyles
}

接着,我们使用泛型改造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface WithStylesProps {
styles?: object
}

const withStyles = <P extends WithStylesProps>(stylesProp: object) => (
WrappedComponent: React.FC<P>
) => {
const WrapCom = React.forwardRef<React.FC<P>, P>(function WrapCom(
props,
ref
) {
const { styles, ...otherProps } = props
const comStyles = Object.assign({}, stylesProp, styles)
return (
<WrappedComponent styles={comStyles} {...(otherProps as P)} ref={ref} />
)
})
return WrapCom
}

用法

1
const ComponentWithStyles = withStyles<ComponentProps>(styles)(Component)

使用的时候可以传入源组件的 Props 类型定义: ComponentPropsm,这样,使用 HOC 的时候就保留了源组件的类型提示了。

window.fetch 的简单封装

背景

项目中存在大量的数据请求逻辑,所以需要对 fetch 进行简单封装

用法

1
2
3
fetch('//fts-test.yy.com/rank/activity190520/activity_status').then((res) => {
console.log(res)
})

我们知道,Response 类的 json()方法返回默认的类型推断为 any,所以我们无法知道接口返回的 json 数据类型。

使用泛型:对 window.fetch 进行简单封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import fetch from 'isomorphic-fetch'
import fetchJsonp from 'fetch-jsonp'
import { stringify } from 'qs'

// 这里使用到泛型接口
export interface FrResponse<T = any> {
data?: T
err?: any
}

export interface FrPromise<T = any> extends Promise<FrResponse<T>> {}

// 此处省略部分次要代码

export default function request<T = any>(
url: string,
{
jsonp,
query,
...options
}: {
jsonp?: boolean
query?: object
} = {}
): FrPromise<T> {
const fetchFunction = jsonp
? fetchJsonp
: process.env.NODE_ENV !== 'production'
? window.fetch
: fetch

return fetchFunction(url + formatQuery(query), {
credentials: 'include',
...options,
})
.then((res) => res && checkStatus(res, jsonp))
.then((response) => response.json())
.then((data: T) => ({ data }))
.catch((err) => ({ err }))
}

使用 ts 之后,我们可以简单通过泛型约束后台返回的 JSON 数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface TypeActivityStatus {
status: number
msg: string
list: null | []
current: number
begin: number
end: number
stage: number
stageBegin: number
stageEnd: number
}

fetch<TypeActivityStatus>(
'//fts-test.yy.com/rank/activity190520/activity_status'
).then((res) => {
// 此处会有类型提示
resolve(res.data && res.data.status)
})

参考

深入理解 TypeScript

React Higher-Order Components in TypeScript