对于初学Typescript的同学来说,声明文件一定是一个让人十分头疼、但是怎么也绕不开的问题。声明文件的书写和声明合并在Typescript官方文档里面已经有比较详细的介绍,这里不再陈述。我们重点讨论的是如何扩展第三方模块中的类型。
类型扩展的基本原则
如何扩展第三方模块中的类型,有三条基本原则:
- 同模块:声明合并只能在同一个模块中进行
- 同路径:声明的模块路径必须与目标类型(你将要扩展的类型)的原始声明文件路径保持一致
- 同书写方式:声明书写方式必须与目标类型一致
下面来详细展开
原则一:同模块
声明合并只能在同一个模块中进行。意思是说,在扩展一个类型之前,你需要先引入这个类型所在的模块。
在下面这个例子中,我们需要为接口Foo
扩展一个属性Bar
,Foo
是在moduleOfFoo
中声明的,为此我们需要先引入moduleOfFoo
:
// 引入`Foo`所在的模块`moduleOfFoo`,这一步非常重要
import 'moduleOfFoo'
然后我们需要声明一个同名的模块,在模块内部进行Foo
的声明合并
// 引入`Foo`所在的模块`moduleOfFoo`,这一步非常重要
import 'moduleOfFoo'
// 声明同名模块
declare module 'moduleOfFoo' {
// 在这个空间内才可以进行声明合并
interface Foo {
Bar: any
}
}
原则二:同路径
声明的模块路径必须与目标类型(你将要扩展的类型)的原始声明文件路径保持一致。
我们来看一个例子:
首先我们在a.d.ts
中声明了interface A
// a.d.ts
export declare interface A {
a: number
}
b.d.ts
引用了A
然后导出
// b.d.ts
export { A } from './a'
现在我们来扩展interface A
import './b'
declare module './a' {
interface A {
test: number
}
}
注意这里declare module './a'
,因为interface A
就是在'./a'
中定义的,必须在这个模块中才能够合并声明。
顺便说一下,这里的import './b'
,改成import './a'
也是可以的,因为都能达到引入interface A
的目的。唯有declare module './a'
不可以改成declare module './b'
,因为'./b'
不是interface A
的原始声明文件。
原则三:同书写方式
声明书写方式必须与目标类型一致。这里主要是说namespace嵌套关系要保持一致。
在下面这个例子中我们将要为joint.dia.CellView
扩展两个方法getData
和setData
。
通过观察joint.d.ts
,我们得知CellView
嵌套了两层namespace
:
export namespace dia {
// ...
export namespace CellView {
// ...
}
// ...
}
所以我们在合并声明的时候,也需要嵌套两层同样的namespace
// 扩展jointjs
import jointjs from 'jointjs'
declare module 'jointjs' {
namespace dia {
interface CellView {
getData: (key?: string) => any
setData: (data: any, value?: any) => void
}
}
}
Typescript声明合并的规则在官方文档有详细的解释,大家感兴趣可以去看看。需要注意的是:
- 声明合并无法覆盖原有的类型
- 类不能与其它类或变量合并
// a.d.ts
export declare interface A {
a: number
b: number
}
export declare let B: number
export declare class C {
a: number
}
我们希望将A.a
和B
的类型改为string
,直接覆盖声明是无效的:
// custom.d.ts
import './a'
declare module './a' {
// 直接覆盖属性a无效
interface A {
a: string
}
// 直接覆盖类型B无效
let B: string
}
如果你实在需要覆盖A.a
的类型,可以考虑使用继承:
// code.ts // 这里不是声明文件,是实实在在的ts代码
import { A as _A } from './a'
export interface A extends _A {
a: string
}
当然这里也并没有覆盖A.a
的类型,不过你可以使用这个新的code.ts
中的interface A
。
现在我们需要扩展class C
的实例属性和静态属性,直接用class
覆盖是无效的
// custom.d.ts
import './a'
declare module './a' {
// 直接覆盖class无效
class C {
b: number
static c: number
}
}
我们可以用interface
来扩展class
的实例属性,用namespace
来扩展class
的静态属性:
// custom.d.ts
import './a'
declare module './a' {
// 使用interface扩展class的实例属性
interface C {
b: number
}
// 使用namespace扩展class的静态属性
namespace C {
let c: number
}
}
如果你在扩展第三方类型的时候遇到问题,请参考以上这三条原则,相信你能够找到答案~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。