TypeScript从d.ts说起
TypeScript(以下简称TS),作为JavaScript(以下简称js)的一个超集,已经深受大家的喜爱了。他解决了js当中没有类型系统的问题。既提供了静态检查,也为开发过程中带来了代码提示。
显然我们很多项目依然是js作为主力开发的语言。那么当我们也想享受TS带来的便利,或者当我们新的项目想使用TS无法兼容旧的js库时。我们应该怎么做呢?
答案就是d.ts。TS为我们提供了一种方法为原本的js代码去声明类型,这种方法就是去编写d.ts文件。
三种方式
对于d.ts有三种方式来使用它为我们的js代码添加类型信息。
1.在原本的js文件路径添加同名.d.ts文件
编辑器会在你导入文件时寻找到同一路径下的d.ts文件,并将你声明的类型信息和你的js文件相对应。
优点:简单方便。
缺点:文件多了之后不便于管理。
2.在package.json当中指定types文件夹
你可以在项目的package.json当中通过types属性指定你的类型信息路径。编辑器依然会正确识别你导入文件的同名.d.ts文件中声明的类型信息。
优点:集中管理不和其他文件混杂
缺点:仅适用于为自己发布的npm包有效。
3.编写专门的@types包
我们可以通过单独为我们的包编写另一个types包,另立一个项目。编辑器会自动从node_modules文件夹下查找@types文件夹中对应的项目来获取类型信息。这种方式一般适用于为不在自己掌握的第三方库添加类型信息。
优点:能适用于第三方库
缺点:维护成本较大
值得注意的一些问题
1.对于package的browser项无法识别。
编辑器是没法知道你现在写的文件是会用到服务端还是浏览器端的。因此如果你使用了browser项,编辑器是不会读取到你写在同一目录下的同名d.ts文件。只能通过在main配置项的同一目录下同名文件同时声明两个文件中的类型。
2.方法入参是一个对象。
当方法的入参是一个对象的时候,通常我们可能会把他声明成一个接口或类型。但是这样的话,当代码提示的时候只会提示到你的参数属于某一个类型,而不会提示出这个类型具体有哪些参数。因此,建议如果这个对象不是过分复杂,可以直接将他写在入参里。
declare interface dataParams {
method: 'GET' | 'POST',
action?: string,
qs?: object,
form?: object,
restfulParams?: object,
json?: object,
body?: object | string,
req?: object,
headers?: object,
}
/**
* 请求
* @param params 参数
*/
declare function isomorphicData(params: dataParams): Promise<Object>;
// 推荐做法
/**
* 请求
* @param params 参数
*/
declare function isomorphicData(params: {
method: 'GET' | 'POST',
action?: string,
qs?: object,
form?: object,
restfulParams?: object,
json?: object,
body?: object | string,
req?: object,
headers?: object,
}): Promise<Object>;
3.声明合并
在js当中,我们时常会在声明了一个方法之后再为这个方法添加一些属性和方法。而在TS声明当中,方法并不能为其声明属性或方法。因此我们需要使用声明合并的方式来为其添加声明。
//js代码
async function isomorphicData(method = 'GET') {
...
return data;
}
isomorphicData.setDirname = (dirname) => {
config.actionDirname = dirname;
};
// d.ts代码
/**
* 请求
* @param params 参数
*/
declare function isomorphicData(params: {
method: 'GET' | 'POST',
}): Promise<Object>;
declare namespace isomorphicData {
/**
* 设置数据处理程序目录,默认为projectDir / data
* 已弃用,请改用init
* @param dirname 请求配置所在路径
*/
function setDirname(dirname: string): void;
}
值得注意的是并不是所有的类型都支持合并声明。
4.泛型系统
其实熟悉其他有类型的语言的同学,应该对泛型系统很熟悉了。泛型其实本质上就是一种多态。
现在我们有一个函数,接受一个参数,返回一个参数。在不用多态的方式下我们可能会写成:
declare function identity(arg:any):any
这样子我们就丢失了它输入的类型就是他返回的类型的信息,那么我们也可以通过重载来写全。
declare function identity(arg:number):number
declare function identity(arg:Function):Function
declare function identity(arg:String):String
...
但是这样很啰嗦,而且也不可能写全所有类型。因此我们还是需要用到泛型。
declare function identity<T>(arg:T):T
这里声明了一个T的泛型,接受一个T类型的arg,返回一个T类型的值。当你调用这个方法时,不需要主动去说明T泛型的具体类型。TS会根据arg的值来做类型推断。
4.类型兼容
对于d.ts的类型文件来讲,如果我们只是使用在js当中来提供类型提示的话,类型兼容并不是很重要。因为js并不会对他进行类型检查。但是在TS当中使用第三方包时的d.ts类型文件就非常重要了。
首先是可选属性:对于一些类型可能并不是所有属性都是必须的。所以我们可以通过?设置可选属性来通过类型判断。
declare interface cat {
eyes:String,
name?:String
}