本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2022-07-12
如何扩展第三方模块中的类型,有三条基本原则:
声明合并只能在同一个模块中进行。意思是说,在扩展一个类型之前,你需要先引入这个类型所在的模块。
// 为接口Foo扩展一个属性Bar,Foo是在moduleOfFoo中声明的
// 为此我们需要先引入moduleOfFoo 这一步非常重要
import 'moduleOfFoo';
// 声明同名模块
declare module 'moduleOfFoo' {
// 在这个空间内才可以进行声明合并
interface Foo {
Bar: any
}
}
举例,扩展axios
import { AxiosRequestConfig } from 'axios';
declare module 'axios' {
export interface AxiosInstance {
<T>(config: AxiosRequestConfig): Promise<T>;
}
}
首先我们在a.d.ts中声明了interface A,b.d.ts引用了A然后导出,扩展interface A;
// a.d.ts
export declare interface A {
a: number
}
// b.d.ts
export { A } from './a';
// index.d.ts
import './a';
// import './b' 也行, 因为在b也导入了a,也能达到引入 interface A 的目的
declare module './a' {
interface A {
test: number
}
}
声明书写方式必须与目标类型一致。这里主要是说 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
}
}
}
声明合并无法覆盖原有的类型
// a.d.ts
export declare interface A {
a: number
b: number
}
export declare let B: number
export declare class C {
a: number
}
// custom.d.ts
import './a'
declare module './a' {
// 直接覆盖属性a无效
interface A {
a: string
}
// 直接覆盖类型B无效
let B: string
// 直接覆盖class无效
class C {
b: number
static c: number
}
// 使用interface扩展class的实例属性
interface C {
b: number
}
// 使用namespace扩展class的静态属性
namespace C {
let c: number
}
}
如果你实在需要覆盖A.a的类型,可以考虑使用继承:
// code.ts 这里不是声明文件,是实实在在的ts代码
import { A as _A } from './a'
export interface A extends _A {
a: string
}
// /src/api/server-api.ts 导出的模块
// /src/api/server-api.d.ts 声明文件
declare module '@/src/api/server-api.ts' { // 其他文件引入的路径
export default class ServerApi extends Vue {
getData(): void;
}
}
比如,class同时出现在类型和值列表里。class C { }
声明创建了两个东西:类型C指向类的实例结构,值C指向类构造函数。枚举声明拥有相似的行为。
export var Bar: { a: Bar };
export interface Bar {
count: number;
}
import { Bar } from './foo'; // 可以解构引入
let x: Bar = Bar.a;
// x 指定为 Bar 类型, Bar.xx 中的 Bar 却是值
console.log(x.count);
有一些声明能够通过多个声明组合。 比如,class C { }
和interface C { }
可以同时存在并且都可以做为C类型的属性。
只要不产生冲突就是合法的。一个普通的规则是:
type s = string
),命名空间永远不会发生冲突。举例:
interface Foo {
x: number;
}
// ... elsewhere ...
interface Foo {
y: number;
}
class Foo {
z!: number;
}
let a: Foo = {x: 1, y: 2, z: 3};
console.log(a.x + a.y + a.z); // OK
namespace声明可以用来添加新类型,值和命名空间,只要不出现冲突。
class C {
}
// ... elsewhere ...
namespace C {
export let x: number;
export interface D { }
}
let y: C.D; // OK
let z: C.x; // OK
在这个例子里,直到我们写了namespace声明才有了命名空间C。做为命名空间的C不会与类创建的值C或类型C相互冲突。namespace C 是一个命名空间的同时也是一个值,因为它的声明包含了一个另一值 x。