引入模块文件
以这三个文件为例,分别以模块导出声明 (External Module Definition) 和 全局类型声明(Global Type Definition) 两种写法编写 Definition。
入口文件引用模块:
// file: index.js
var sample = require("sample");
var lib1 = require("sample/lib1");
var lib2 = require("sample/lib2");
假设以下为三个文件代码,后面以这三个文件基础编写Definition:
// file: ./node_modules/sample/index.js
var abc = 321;
exports.setABC = function(abcValue) {
abc = abcValue;
};
exports.getABC = function() {
return abc;
};
exports.defaultABC = abc;
// file: ./node_modules/sample/lib1.js
var Hello = (
function () {
function Hello(a) {
this.valueA = a;
}
Object.defineProperty(
Hello.prototype,"a", {
get: function () {
return this.valueA;
},
enumerable: true,
configurable: true
});
Hello.initClass = function () {
Hello.initCount = 0;
};
/* 假设这是一个重载函数,支持多种调用方式 */
Hello.prototype.setup = function (x, b) {
if (b === void 0) {
b = null;
}
return false;
};
return Hello;
}());
exports.Hello = Hello;
// file: ./node_modules/sample/lib2.js
var randStrSeed = "abcdefghijklmnopuvwxyz012345";
function randomString(length) {
var ret = "";
while (length-- > 0) {
ret += randStrSeed[Math.floor(Math.random() * randStrSeed.length)];
}
return ret;
}
module.exports = randomString;
这是三个典型的模块类型:
- 第一个导出了变量和函数
- 第二个导出了一个类
- 第三个则将一个函数作为一个模块导出
全局类型声明写法
假如上面3个文件同属一个模块sample,但它并不是我们自己发布到npm的包,即我们无权为它建Definition,所以我们用全局声明写法。
如果不是很复杂,我们用一个.d.ts文件就可以了。
第一个文件是模块入口文件,导出了变量和函数,可以直接当作模块sample:
declare module "sample" {
// 导出函数 setABC
export function setABC(abcValue: number): void;
// 导出函数 getABC
export function getABC(): number;
// 导出变量 defaultABC
export let defaultABC: number;
}
第二个文件导出了两个类,可以当作模块"sample/lib1"。
这个类里面有构造函数,有静态方法,有普通方法,有属性,也有静态属性,还有 getter。
类有两种声明编写方式:标准式和分离式。
保准方式:
declare module "sample/lib1" {
export class Hello {
private valueA;
b: number;
static initCount: number;
a: number;
constructor(a: number);
static initClass(): void;
// 假设这是重载函数,支持多种调用方式
setup(name: string): boolean;
setup(name: string, age: number): boolean;
}
}
但是这种写法也有不便的地方,比如扩展类不方便——JavaScript允许你随时扩展一个类的原型对象实现对类的扩展,或者随时给类添加静态成员。标准式写法很难实现扩展,因为你无法重复声明一个类。
分离式声明:
在这之前我们要理解,JS 的类是用函数实现的,即是说 JS 的类本质上就是一个构造函数 + Prototype。Prototype 的成员就是类的成员;而类的静态方法就是这个构造函数对象本身的成员方法。
declare module "sample/lib1" {
// 在分离式写法里面,一个类的 Prototype 的声明是一个直接以类名称为名的interface。
// 成员函数和变量/getter/setter 都行写在 prototype的接口里面。
// 注:类原型的interface取名与类一致
export interface Hello {
// 接口里面只写类的 public 属性
b: number;
// Getter/Setter 直接成属性即可
a: number;
// 重载函数的声明写法
setup(name: string): boolean;
setup(name: string, age: number): boolean;
}
// 在分离式写法里面,一个类的构造函数对象也是一个 interface ,但是对
// 其命名无具体要求,合理即可。
// 把类的静态方法和属性都写在这里面。
export interface HelloConstructor {
// 静态属性
initCount: number;
// 静态方法
initClass: void;
// 构造函数!使用 new 代替 constructor,并声明其返回值类型是该类的Prototype。
new(a: number): Hello;
}
// 将 Hello 覆盖声明为HelloConstructor。
// 这样,在需要作为类使用的时候它就是 HelloConstructor,需要作为接口使用的时候就是 Hello(原型接口)。
export let Hello: HelloConstructot;
}
第三个文件,直接将一个函数作为模块导出。
declare module "sample/lib2" {
let randomString: (length: number) => string;
export = randomString;
}
最后把 3 个模块的声明合并成一个文件 sample.d.ts,在文件里用三斜线指令引用即可。
模块导出声明写法
模块导出声明写法里面不用注明是哪个模块,一般给每个导出的文件都配备一个以 .d.ts 为后缀的 Definition。
文件 ./node_modules/sample/index.d.ts
// File: ./node_modules/sample/index.d.ts
// 导出函数 setABC
export declare function setABC(abcValue: number): void;
// 导出函数 getABC
export declare function getABC(): number;
// 导出变量 defaultABC
export declare let defaultABC: number;
文件 ./node_modules/sample/lib1.d.ts
// File: ./node_modules/sample/lib1.d.ts
export class Hello {
private valueA;
b: number;
static instCount: number;
a: number;
constructor(a: number);
static initClass(): void;
//假设这是一个重载函数,支持多种调用方式
setup(name: string): boolean;
setup(name: string, age: number): boolean;
}
文件 ./node_modules/sample/lib2.d.ts
// File: ./node_modules/sample/lib2.d.ts
let randomString: (length: number) => string;
export = randomString;
编写Definition注意事项
- 不要使用内层declare
只能在 Definition 的顶层使用 declare,比如下面的写法都是错误的:
declare module "sample" {
// 此处应当使用 export
declare let a: string;
}
declare namespace Sample {
// 此处应当使用 export
declare let a: string;
}
- 避免全局污染
虽然全局声明写法允许你引入名称到全局命名空间中,但这也意味着,引入的顶层名称都是全局的。所以应该将所有的模块内导出的元素都放进模块或者命名空间内:
declare module "sample" {
//仅可通过 import { Person } from "sample" 访问。
export interface Person {
name: string;
}
}
declare namespace Sample {
export interface Animal {
type: string;
}
}
而不是
// 无需 import 即可使用,即全局的
interface Person {
name: string;
}
- 注意声明冲突
- 模块名称要区分大小写!
网友评论