刚开始写声明文件时,不知道如何下手。但是,随着反复的实验,以及参考一些公开的声明文件,发现写声明文件也不是那么难。只要熟悉Typescript,了解
Typescript
与javascript
之间的异同,很容易就能够根据API写出对应的声明文件来
声明文件是以.d.ts
为后缀的文件,文件名称与javascript
文件名称一致。声明文件主要是解决js
文件在Typescript
中的使用问题,有了声明文件,编译器就可以对引入的js
库做类型检查,同时支持代码智能提示。
下面,就通过例子来讲解声明文件是如何编写的吧。
例1:关于方法的定义
API:
getAccount(id)
getInfo()
声明:
declare getAccount(id: number):void
declare getInfo():any
方法只要在方法前加入declare
即可暴露该方法。其次,如果方法有返回参数,可使用any
,如果没有,可使用void
,不写即为any
,如果知道具体的类型,也可以填具体的类型。
例2:关于类型
2-1: 简单声明
常用的基本类型有 元组、数组、string、number、boolean,另外还有枚举
API:
getName()
输出:
"somenone"
声明:
declare getName():string
//或
declare getName():any
2-2:多种类型
API:
getExtraData(id)
输出:
"somenone"
或者
1
或者
undefine
声明:
declare getExtraData(id:number): string|number|undefine
//或
declare getExtraData(id:number):any
//或
declare getExtraData(id:any):any
如果参数非必须,则可以这样定义
declare getExtraData(id?:number): string|number|undefine
任何类型都可以使用
any
替代,如果不知道是什么类型,或者不关心返回类型,或者返回类型太复杂,类型不止一种,这时候,通常用any
替代是一个比较省事的方法。当然,最好是越详细越好,这样可以方便编译器做类型检查以及代码提示,从而规范自己的代码
接下来的例子,类型同样可以使用any
替代
2-3: 返回数据或参数为json
对象
一般在声明文件中,返回的json
数据可以单独定义成类型,基本类型也可以取别名。
declare type Name=string;
declare type Info = {
name: string
}
举例:
API:
# 方法
getAccount(id)
# 输出
{
"name":"someone",
"age":12,
"gender":true,
"extra":{
"loginTome": 1
}
}
声明:
declare type Result = {
name: string,
age: string,
gender: boolean,
extra: any
}
declare getAccount(id:number): Result
同样Result
可以写成这样:
declare type ResultItem = {
loginTome: number
}
declare type Result = {
name: string,
age: number,
gender: boolean,
extra: ResultItem
}
如果图省事,可以这样定义:
declare type Result = {
name: string,
age: number,
gender: boolean,
extra:{
loginTome: number
}
}
也可以使用interface
来定义:
declare interface Result{
name: string,
age: number,
gender: boolean,
extra:{
loginTome: number
}
}
对于一些非必需的参数可以使用?
,一些多类型的参数,可以使用|
.
?
= |undefine
declare type Result = {
name: string,
age: number,
gender: boolean,
extra?: {
loginTome: number
}
}
// 或
declare type Result = {
name: string,
age: number,
gender: boolean,
extra : {
loginTome: number
}|undefine
}
由上述可以找到,声明文件的定义可以根据每个人的需求去定义,并不需要完全一致。甚至,如果方法太多,一些用不到的方法可以不声明。
同理,参数为json
也是一样这样定义类型
例3:关于类
一般类使用
class
或者interface
定义,如果类中有静态方法 —— 即无须实例化对象即可使用的属性和方法,则需要将这些方法写到namespace
中
其中声明文件最主要的一部分,就是类的声明。
例3-1:基本的类
var a = new Account(1);
console.log(a.name)
console.log(a.getExtra())
console.log(Account.TypeOfUser)
Account.login(a.id)
输出:
"someone"
"{
"loginTime": 1
}"
"USER"
声明:
declare class Account{
constructor(id: number);
getExtra(): Account.ExtraData
name:string
id: number
}
declare namespace Account{
interface ExtraData{
loginTime: number
}
const TypeOfUser:string
function login(id:number):any
}
解析:
这里的ExtraData
名字可以随意取,不要重名即可,也不一定放在Account
的命名空间中,但是一般都放在命名空间中,这样就不会引起过多的全局变量,同时大大的减少重名的变量
例3-2 如果例3-1
中的new
去掉,该如何声明呢?
var a = Account(1);
console.log(a.name)
...
declare function Account(id: number): Account;
declare interface Account {
getExtra(): Account.ExtraData
name: string
id: number
}
declare namespace Account {
interface ExtraData {
loginTime: number
}
const TypeOfUser: string
function login(id: number): any
}
例4: 方法的“重载”
声明文件允许出现多个相同名称的方法,在类和接口里面同样是允许这样做的
declare getExtraData(id?:number): any
那么前面提到的getExtraData
可以有新的写法
declare getExtraData(id:number): any
declare getExtraData(): any
这样,就可以很方便的区分可以传递不同数量参数的方法的情况
例5: 关于继承
API:
getData(1)
getResult(1)
输出:
// getData
{
"id":1,
"time": 0,
"errCode": 0,
"res":{
"name":"someone"
}
}
// getResult
{
"id":1,
"time": 0,
"errCode": 0,
"res":{
"age": 1
}
}
跟例2-3
一样,可以分别定义一个类型:
declare interface Data {
id: number,
time: number,
errCode: number,
res:{
name: string
}
}
declare interface Result {
id: number,
time: number,
errCode: number,
res:{
age: number
}
}
但是这样,显然不是很好,因为可以看出来,Data
和Result
有很多相似的地方,所以应该是可以优化的,下面我就来介绍一下几种优化方法
方法1:一劳永逸的方法
把变化的部分类型定义为any
,这样的话无论res
如何变化,同一类型都可以用Base
来作为返回结果(也可以是方法参数)的类型
declare interface Base {
id: number,
time: number,
errCode: number,
res: any
}
方法2:兼容模式
使用|
,可以不断的添加新的类型,不过在使用上会带来诸多不便,不建议使用
declare interface DataItem{
name: string
}
declare interface ResultItem{
age: number
}
declare interface Base {
id: number,
time: number,
errCode: number,
res: DataItem|ResultItem
}
方法3:兼容模式2
在res
内把所有可能的参数都加上,如果所有情况都出现,则无需加?
,否则就要加?
。这种方式也不太建议使用,不过在某些场合还是可以用到的。比如res
中也有很多相同的字段,或者大部分都差不多。使用的时候带?
的,需要判断是否为undefine
。
declare interface Base {
id: number,
time: number,
errCode: number,
res: {
name?: string
age?: number
}
}
方法4:继承
这种方式感觉好像还麻烦了些,不过却是一个好的结构,没有出现重复的代码,也就意味着出现错误的几率会变小,同时类型越多,这种写法的优势就越明显,还是有一定的借鉴价值的。
interface Base {
id: number,
time: number,
errCode: number
}
interface DataItem{
name: string
}
interface ResultItem{
age: number
}
declare interface Data extends Base {
res: DataItem
}
declare interface Result extends Base {
res: ResultItem
}
方法5:继承 + 泛型
其中Item
可以写上相同的属性,也可以是空接口。
利用泛型后,等于进一步优化了方法4
。res
是相同类型的都有的属性,但是其中结构又各有差异,所以用泛型是最好的选择。这个也是比较推荐的一种写法。
interface Item { }
interface Base {
id: number,
time: number,
errCode: number,
res: Item
}
interface DataItem extends Item{
name: string
}
interface ResultItem extends Item{
age: number
}
declare interface Data extends Base {}
declare interface Result extends Base {}
总结
写了这么多,可以知道,其实声明文件编写并不是那么严格,但是一个好的声明文件还是要越详细越好。如果是个人使用,方法和属性太多太杂的话,就可以考虑忽略掉那些不会用到的方法和属性,毕竟没必要花太多时间来编写声明文件。如果用到了,在添加上对应的声明即可。
同时,声明文件的编写,可以充分利用Typescript
的特性,也要熟悉javascript
的语法,这样就可以将js
库的接口很好的对接上ts
了。