现在有这样一个任务:给一个用 javascript 包写一个类型文件。
这个包导出了一个函数calculator
,它具有如下功能:
calculator.js
文件
// operator: 只能是 plus | minus
function calculator(operator, numbers) {
console.log(operator, numbers)
return
}
calculator.plus = (numbers) => {
return numbers
}
calculator.minus = (numbers) => {
return numbers
}
export default calculator
先分析下calculator
的结构。
它首先是一个函数,这个函数传递两个参数,第一个参数只能是plus
和minus
,第二个参数是一个数字数组。
其次,它有两个属性,分别是plus
和minus
,参数都是一个数字数组。
下面就来一步步完善这个类型文件。
第一步,创建一个 calculator.d.ts
文件。因为 calculator
本身是一个函数,可以先定义一个函数类型:
calculator.d.ts
文件
type IOperator = 'plus' | 'minus'
type ICalculate = (operator: IOperator, numbers: number[]) => number
declare const calculator: ICalculate
export default calculator
配置tsconfig.json
:
"files": ["calculator.d.ts"]
index.ts
文件
可以看到,我们写的类型声明文件生效了。
注意,这里在index.ts
引入calculator.js
的函数时,calculator.d.ts
文件中也一定要加上export default calculator
,否则会报错,报calculator.d.ts
不是一个模块。
但当我们访问这个函数的属性时,会报错:
这是因为我们只声明它是一个函数,calculator
还有两个属性方法,那怎么办呢?使用 interface
:
interface Icalculator {
(operator: Operator, numbers: number[]): number
plus(numbers: number[]): number
minus(numbers: number[]): number
}
这样就大功告成了,最后把这个声明文件提交到@types
组织,然后通过npm install @types/calculator
下载,这样 typescript 会自动读取类似声明文件。
注意:提交到@types时,类型文件的命名一定是index.d.ts
同时,我们的引入方式就变成了import calculator from 'calculator'
。
下面让我们来看一个axios
库类型定义文件是怎么写的。下面对axios
类型声明文件进行精简,保留了重要部分。
// 定义请求头的格式,即 key 只能是字符串,value 是一个联合类型 string | number | boolean
export type AxiosRequestHeaders = Record<string, string | number | boolean>;
// 定义了响应头的格式,即 key 和 value只能是字符串,同时利用 &(交叉类型) 把多个类型合并成一个类型
export type AxiosResponseHeaders = Record<string, string> & {
"set-cookie"?: string[]
};
// 对请求的参数进行转换,此处对应的就是转换函数
export interface AxiosRequestTransformer {
(data: any, headers?: AxiosRequestHeaders): any;
}
// 定义了所有请求方法的类型
export type Method =
| 'get' | 'GET'
| 'delete' | 'DELETE'
| 'head' | 'HEAD'
...
// 定义了所有返回数据的类型,可以是二进制,也可以是json对象
export type ResponseType =
| 'arraybuffer'
| 'blob'
| 'document'
| 'json'
...
// 请求配置AxiosRequestConfig是一个接口,接口传入了一个泛型,这个泛型就可以流入到里面的属性或值里面。
export interface AxiosRequestConfig<D = any> {
url?: string;
method?: Method | string;
baseURL?: string;
headers?: AxiosRequestHeaders;
params?: any;
data?: D;
responseType?: ResponseType;
...
}
// 请求返回的数据类型,定义了一个interface,里面有data,status等,同时传入了一个泛型,这个泛型可以流入到data里面,这样我就定义了data的类型
export interface AxiosResponse<T = any, D = any> {
data: T;
status: number;
statusText: string;
headers: AxiosResponseHeaders;
config: AxiosRequestConfig<D>;
request?: any;
}
// 定义了一个Axios的类,这个类定义了构造函数constructor,拦截器interceptors,以及一些调用方法get/post等
export class Axios {
constructor(config?: AxiosRequestConfig);
defaults: AxiosDefaults;
interceptors: {
request: AxiosInterceptorManager<AxiosRequestConfig>;
response: AxiosInterceptorManager<AxiosResponse>;
};
get<T = any, R = AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
post<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
...
}
// AxiosPromise 继承于 Promise<AxiosResponse<T>>,Promise<AxiosResponse<T>>里面传入了泛型,这样泛型流入到AxiosResponse里面
export interface AxiosPromise<T = any> extends Promise<AxiosResponse<T>> {
}
// 定义了一个Axios实例的接口
export interface AxiosInstance extends Axios {
(config: AxiosRequestConfig): AxiosPromise;
(url: string, config?: AxiosRequestConfig): AxiosPromise;
}
export interface AxiosStatic extends AxiosInstance {
create(config?: AxiosRequestConfig): AxiosInstance;
Axios: typeof Axios;
...
}
declare const axios: AxiosStatic;
export default axios;
首先我们引入import axios from 'axios'
,这个 axios
的类型就是 AxiosStatic
,这个类型上有个 create
的方法:
export interface AxiosStatic extends AxiosInstance {
create(config?: AxiosRequestConfig): AxiosInstance;
Axios: typeof Axios;
...
}
执行 create
方法后,返回一个类型为 AxiosInstance
的变量:
export interface AxiosInstance extends Axios {
(config: AxiosRequestConfig): AxiosPromise;
(url: string, config?: AxiosRequestConfig): AxiosPromise;
}
这个变量首先是一个函数,这个函数可以这么使用(config: AxiosRequestConfig): AxiosPromise;
,也可以这么使用(url: string, config?: AxiosRequestConfig): AxiosPromise;
,同时它也继承了Axios
类型,Axios
类型有get
, post
等方法,所以它就有了如下使用方法:
import axios from 'axios'
const axiosInstance = axios.create()
// 发送请求的三种方式
axiosInstance({ url: 'test.url'})
axiosInstance(url: 'test.url', config: { method: 'get' })
axiosInstance.get('test.url')
可以发现,我们通过解析 axios
的类型文件,就能基本把这个库的功能都搞清楚了,这就是 ts 所带来的便利。