泛型是强类型语言中比较重要的一个概念,合理的使用泛型可以提升代码的可复用性,让系统更加灵活。
泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型。
泛型通过一对尖括号来表示(<>),尖括号内的字符被称为类型变量,这个变量用来表示类型。
function copy<T>(arg: T): T {
if (typeof arg === 'object') {
return JSON.parse(
JSON.stringify(arg)
)
} else {
return arg
}
}
定义了泛型函数后,可以用两种方法使用。 第一种是,传入所有的参数,包含类型参数:
let value = copy<string>("myString");
这里明确指定了T是string类型,并做为一个参数传给函数,使用了<>括起来而不是()。
第二种方法更普遍。利用了类型推论 -- 即编译器会根据传入的参数自动地帮助我们确定T的类型:
let value= copy("myString");
注意我们没必要使用尖括号(<>)来明确地传入类型;编译器可以查看myString的值,然后把T设置为它的类型。 类型推论帮助我们保持代码精简和高可读性。如果编译器不能够自动地推断出类型的话,只能像上面那样明确的传入T的类型,在一些复杂的情况下,这是可能出现的。
工具泛型
Partial
type Partial<T> = {
[P in keyof T]?: T[P]
}
Partial 用于将一个接口的所有属性设置为可选状态,首先通过 keyof T,取出类型变量 T 的所有属性,然后通过 in 进行遍历,最后在属性后加上一个 ?。
我们通过 TypeScript 写 React 的组件的时候,如果组件的属性都有默认值的存在,我们就可以通过 Partial 将属性值都变成可选值。
import React from 'react'
interface ButtonProps {
type: 'button' | 'submit' | 'reset'
text: string
disabled: boolean
onClick: () => void
}
// 将按钮组件的 props 的属性都改为可选
const render = (props: Partial<ButtonProps> = {}) => {
const baseProps = {
disabled: false,
type: 'button',
text: 'Hello World',
onClick: () => {},
}
const options = { ...baseProps, ...props }
return (
<button
type={options.type}
disabled={options.disabled}
onClick={options.onClick}>
{options.text}
</button>
)
}
Required
type Required<T> = {
[P in keyof T]-?: T[P]
}
Required 的作用刚好与 Partial 相反,就是将接口中所有可选的属性改为必须的,区别就是把 Partial 里面的 ? 替换成了 -?。
Record
type Record<K extends keyof any, T> = {
[P in K]: T
}
Record 接受两个类型变量,Record 生成的类型具有类型 K 中存在的属性,值为类型 T。这里有一个比较疑惑的点就是给类型 K 加一个类型约束,extends keyof any,大致就是类型 K 被约束在 string | number | symbol 中,刚好就是对象的索引的类型,也就是类型 K 只能指定为这几种类型。
我们在业务代码中经常会构造某个对象的数组,但是数组不方便索引,所以我们有时候会把对象的某个字段拿出来作为索引,然后构造一个新的对象
假设有个商品列表的数组,要在商品列表中找到商品名为 「每日坚果」的商品,我们一般通过遍历数组的方式来查找,比较繁琐,为了方便,我们就会把这个数组改写成对象。
interface Goods {
id: string
name: string
price: string
image: string
}
const goodsMap: Record<string, Goods> = {}
const goodsList: Goods[] = await fetch('server.com/goods/list')
goodsList.forEach(goods => {
goodsMap[goods.name] = goods
})
Pick
type Pick<T, K extends keyof T> = {
[P in K]: T[P]
}
Pick 主要用于提取接口的某几个属性。做过Todo工具的同学都知道,Todo工具只有编辑的时候才会填写描述信息,预览的时候只有标题和完成状态,所以我们可以通过 Pick 工具,提取 Todo 接口的两个属性,生成一个新的类型 TodoPreview。
interface Todo {
title: string
completed: boolean
description: string
}
type TodoPreview = Pick<Todo, "title" | "completed">
const todo: TodoPreview = {
title: 'Clean room',
completed: false
}
Exclude
type Exclude<T, U> = T extends U ? never : T
Exclude 的作用与之前介绍过的Extract刚好相反,如果 T 中的类型在 U 不存在,则返回,否则抛弃。现在我们拿之前的两个类举例,看看 Exclude 的返回结果。
interface Worker {
name: string
age: number
email: string
salary: number
}
interface Student {
name: string
age: number
email: string
grade: number
}
type ExcludeKeys = Exclude<keyof Worker, keyof Student>
// 'salary'
取出的是 Worker 在 Student 中不存在的 salary。
Omit
type Omit<T, K extends keyof any> = Pick<
T, Exclude<keyof T, K>
>
Omit 的作用刚好和Pick相反,先通过 Exclude<keyof T, K> 先取出类型 T 中存在,但是 K 不存在的属性,然后再由这些属性构造一个新的类型。还是通过前面的 Todo 案例来说,TodoPreview 类型只需要排除接口的 description 属性即可,写法上比之前 Pick 精简了一些。
interface Todo {
title: string
completed: boolean
description: string
}
type TodoPreview = Omit<Todo, "description">
const todo: TodoPreview = {
title: 'Clean room',
completed: false
}