Typescript

TS 声明文件

引入/创建声明文件

在使用 TypeScript 写程序的时候,可能会需要使用到 JavaScript 库,因为历史遗留问题,现在非常多的第三方库依然是用 JavaScript 进行编写,但是大多数情况下,库的所有者已经帮你写好了 TypeScript 声明文件,即 .d.ts 文件。

还有一些情况是库中并没有附带 ts 声明文件,但是可以通过:

1
npm i @types/xxx

进行下载该类型的库的声明文件。

上面两种方法都是用在别人已经帮你编写了声明文件的情况下,但是如果上面两种方式都无效,怎么自行编写声明文件呢?

假设现在要编写 wow.js 的声明文件,只需以下两步即可:

  1. 在 src 下新建 types 文件夹,在 types 下新建 wow.d.ts 文件

  2. 在 wow.d.ts 中添加如下内容:declare module "wow.js"

之后即可正常导入 wow.js 了。

那如果想要在原有库的基础上拓展一些类型该怎么办呢?看下面两个例子。


拓展

axios

在使用 axios 库时,我们通常会在响应拦截器中返回 AxiosResponse.data,但是这样的话,在请求完成获取响应数据时会造成类型错误:

1
2
3
4
5
6
7
// 响应拦截器
request.interceptors.response.use((response: AxiosResponse) => {
return response.data
})

// 发起请求
request({}).then((res: AxiosResponse) => {});

这里的 res 实际上是 AxiosResponse.data,但类型检查时它的类型仍然是 AxiosResponse,这就造成了错误。

那要怎么解决呢?我们可以拓展 AxiosResponse 接口。

在 src/types 下新建 axios.d.ts 文件,添加入下内容:

1
2
3
4
5
6
7
8
9
10
import "axios"

declare module 'axios' {
export interface AxiosResponse<T = any> {
code: number;
data?: T;
message?: string;
[key: string]: any;
}
}

注意: 一定要在文件顶部引入 “axios”,否则就是重写 axios 的声明文件了。

如果想要明确 data 的类型,可以这样写:

1
2
3
4
5
6
7
8
9
10
11
import { AxiosResponse } from "axios";

interface Res {
name: string;
age: number;
}

request({}).then((res: AxiosResponse<Res>) => {
res.code;
res.data.name;
})

pinia

使用 pinia 时为 state 声明类型。

在 src/types 下新建 pinia.d.ts 文件,添加如下内容:

1
2
3
4
declare interface UserInfoState {
name: string;
age: number;
}

之后在创建仓库时可直接使用以上的接口。

1
2
3
4
5
6
7
8
import { defineStore } from 'pinia';

const userInfo = defineStore("userInfo", {
state: (): UserInfoState => ({
name: "",
age: 0
})
})

Typscript 工具类

TS 原生工具类

Required
1
2
3
type Required<T> = {
[P in keyof T]-?: T[P];
};

将 T 中的可选类型变为必选类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Person {
name: string;
age: number;
address: string;
email?: string;
phonenumber?: string;
}

type P = Required<Person>
// 结果
type P = {
name: string;
age: number;
address: string;
email: string;
phonenumber: string;
}

Partial
1
2
3
type Partial<T> = {
[P in keyof T]?: T[P];
};

将 T 中的必选类型变为可选类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Person {
name: string;
age: number;
address: string;
email?: string;
phonenumber?: string;
}

type P = Partial<Person>
// 结果
type P = {
name?: string;
age?: number;
address?: string;
email?: string;
phonenumber?: string;
}

Pick
1
2
3
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};

根据给定的 T 中的属性 K 生成一个新的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Person {
name: string;
age: number;
address: string;
email?: string;
phonenumber?: string;
}

type P = Pick<Person, "name" | "address">
// 结果
type P = {
name: string;
address: string;
}

Omit
1
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

根据 T 中不被包含在 K 中的属性生成一个新的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface Person {
name: string;
age: number;
address: string;
email?: string;
phonenumber?: string;
}

type P = Omit<Person, "name" | "address">
// 结果
type P = {
age: number;
email?: string;
phonenumber?: string;
}

Exclude
1
type Exclude<T, U> = T extends U ? never : T;

T U 都是联合类型,根据 T 中不被 U 包含的类型生成一个新的类型。

1
2
3
type P = Exclude<"name" | "email" | "xxx", "name" | "age" | "address">
// 结果
type P = "email" | "xxx"

Extract
1
type Extract<T, U> = T extends U ? T : never;

T U 都是联合类型,根据 T 中被 U 包含的类型生成一个新的类型。

1
2
3
type P = Extract<"name" | "email" | "xxx", "name" | "age" | "address">
// 结果
type P = "name"

TS 拓展工具类

vue 获取组件类型

我们在父组件中定义子组件的模板引用时,如果不设置模板引用的类型我们就无法获取子组件暴露的变量和方法的类型提示。

1
2
3
4
5
6
7
8
9
<template>
<child ref="childRef" />
</template>
<script>
import Child from "./child.vue";
import { ref } from "vue";

const childRef = ref();
</script>

可以通过以下方法获取组件的类型:

1
const childRef = ref<InstanceType<typeof Child>>();

但是这样写类型太长不美观,我们可以将其封装成一个函数:

1
2
3
4
5
import { ref } from "vue";

export function getComponentRef<T extends abstract new (...args: any) => any> (_component: T) {
return ref<InstanceType<T>>();
}

使用:

1
const childRef = getComponentRef(Child);