- 文档
- 教程
【免费下载链接】TypeScript
TypeScript 使用手册(中文版)翻译。http://www.typescriptlang.org
在 TypeScript 生态中,为 JavaScript 库编写.d.ts声明文件是让库获得完整类型提示与静态检查能力的关键环节。本文围绕 TypeScript 使用手册(中文版)中的 module.d.ts 模板 展开,系统讲解该模板的每一处结构、适用场景与背后的 TypeScript 类型系统原理。阅读本文后,你将能够识别模块化代码库,理解声明文件中"值、类型、命名空间"三种含义的组合方式,并根据本模板为任意普通模块编写可发布、可维护的index.d.ts声明文件。
模板定位:何时使用 module.d.ts
在开始逐段拆解之前,先明确该模板在整套声明文件模板中的位置。TypeScript 声明文件按其服务对象分为全局库与模块化库两大类,其中模块化代码库(如绝大多数 Node.js 库、ES 模块库)有四个官方模板可供选择:
- module.d.ts——模块既不可调用、也不可构造时使用,即模块导出的是普通值、函数、类型与命名空间的组合;
- module-function.d.ts——模块本身可作为函数调用(如
const y = x(42)); - module-class.d.ts——模块可用
new构造(如const y = new x('hello')); - module-plugin.d.ts——模块在导入后会修改其他模块(插件模式)。
正如 library-structures.md 所强调的:你应该先阅读module.d.ts以便从整体上了解它们的工作方式。因此本模板是理解其他所有模块类模板的基石。
如何从源码识别一个库是否属于模块化代码库?library-structures.md 给出了可操作的特征:模块化代码库至少包含以下代表性条目之一——无条件的require或define调用、import * as a from 'b';或export c;这样的声明、对exports或module.exports的赋值;它们极少包含对window或global的赋值。凡是符合这些特征、只能运行在模块加载器环境中的库(如必须用 CommonJSrequire加载的express),其声明文件都应以本模板为起点。
模板逐段拆解
module.d.ts模板本体是一个带详细注释的 TypeScript 代码文件,注释以/*~标记开头,方便你在复制后快速定位需要修改或删除的段落。下面按结构逐段说明其含义与处理方式。
1. 文件头:库信息与归属声明
// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~] // Project: [~THE PROJECT NAME~] // Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>前三行是声明文件的元信息注释:分别填写库的名称与可选版本号、所属项目名称以及维护者姓名与主页地址。这部分约定俗成的头部信息不仅用于人类阅读,也便于社区工具与搜索引擎识别声明文件归属。
2. 命名与放置:index.d.ts 规则
/*~ This is the module template file. You should rename it to index.d.ts *~ and place it in a folder with the same name as the module. *~ For example, if you were writing a file for "super-greeter", this *~ file should be 'super-greeter/index.d.ts' */模板明确指出:必须将文件重命名为index.d.ts,并放在与模块同名的文件夹中。例如为super-greeter库编写声明,就应创建super-greeter/index.d.ts。
这与 TypeScript 的模块解析规则完全一致——与 JavaScript 源码结构中index.js的作用一样,index.d.ts是解析import ... from 'super-greeter'时定位到的声明入口。对于发布到 npm 的库,该文件最终会落入@types/包(如@types/super-greeter)或随库源码一起发布。若库本身含有多级子模块结构,global-plugin.d.ts 模板 的"代码库文件结构"一节展示了myLib/foo、myLib/bar/baz对应的声明文件需按同样目录层级铺设foo.d.ts、bar/index.d.ts、bar/baz.d.ts的做法——即声明文件结构应反映源码结构。
3. UMD 全局变量声明:export as namespace
/*~ If this module is a UMD module that exposes a global variable 'myLib' when *~ loaded outside a module loader environment, declare that global here. *~ Otherwise, delete this declaration. */ export as namespace myLib;export as namespace myLib声明该模块在缺少模块加载器的环境(如直接通过<script>标签引入)中会暴露一个名为myLib的全局变量。这是 UMD 模块的典型特征:它既可以用import/require按模块方式使用,又可以在浏览器全局作用域中直接访问。
需要注意的关键判断:只有当你的库确实是 UMD 模块时才保留此行,否则应删除。library-structures.md 给出了识别 UMD 库的方法——查看源码文件顶端是否存在typeof define、typeof window或typeof module之类的环境检测代码:
(function (root, factory) { if (typeof define === "function" && define.amd) { define(["libName"], factory); } else if (typeof module === "object" && module.exports) { module.exports = factory(require("libName")); } else { root.returnExports = factory(root.libName); } }(this, function (b) {大多数流行的库(jQuery、Moment.js、lodash)都提供 UMD 格式包。若你的库不是 UMD,却保留export as namespace,会给使用者带来误导性的全局变量声明。
4. 模块方法:函数导出
/*~ If this module has methods, declare them as functions like so. */ export function myMethod(a: string): string; export function myOtherMethod(a: number): number;对于模块对外暴露的函数方法,直接使用export function声明。此处需要注意 TypeScript 声明文件的写法:函数体不需要实现,只保留签名(参数类型与返回类型)。模板给出了两个示例:
myMethod(a: string): string——接收字符串、返回字符串;myOtherMethod(a: number): number——接收数字、返回数字。
当函数存在多种参数形态时,可以像 module-function.d.ts 中那样声明多个重载签名,TypeScript 会按声明顺序匹配调用:
declare function MyFunction(name: string): MyFunction.NamedReturnType; declare function MyFunction(length: number): MyFunction.LengthReturnType;5. 模块类型:接口导出
/*~ You can declare types that are available via importing the module */ export interface someType { name: string; length: number; extras?: string[]; }通过export interface导出的类型,可以被模块使用者直接导入并使用。模板示例定义了一个someType接口,包含必填的name: string与length: number,以及可选的extras?: string[](?表示该属性可省略)。使用者可以这样写:
import { someType } from 'yourModule'; const item: someType = { name: 'demo', length: 3 };在模块声明文件中,接口、类型别名、枚举、类等都可以通过export暴露给外部,这是模块类模板与全局模板(通过declare namespace暴露)最直观的差异。
6. 模块属性:值导出
/*~ You can declare properties of the module using const, let, or var */ export const myField: number;模块自身除了方法和类型,还可以携带属性值。模板示范了用const声明一个只读属性myField: number。若属性需要可修改,则可改用let或var。这一声明对应着模块的顶层导出对象,例如:
import { myField } from 'yourModule'; console.log(myField); // number7. 子命名空间:export namespace的使用
/*~ If there are types, properties, or methods inside dotted names *~ of the module, declare them inside a 'namespace'. */ export namespace subProp { /*~ For example, given this definition, someone could write: *~ import { subProp } from 'yourModule'; *~ subProp.foo(); *~ or *~ import * as yourMod from 'yourModule'; *~ yourMod.subProp.foo(); */ export function foo(): void; }这是模板中最体现 TypeScript 类型系统精髓的部分:当模块暴露带点号路径的嵌套结构(dotted names)时,需要把这些成员放进namespace声明中。示例中的subProp命名空间导出函数foo(): void,使用者有两种访问方式:
// 方式一:具名导入子命名空间 import { subProp } from 'yourModule'; subProp.foo(); // 方式二:整体导入模块对象 import * as yourMod from 'yourModule'; yourMod.subProp.foo();命名空间在这里同时充当"类型容器"与"值容器":subProp作为值时承载foo函数,而命名空间内部的接口、类型别名则可以通过yourModule.subProp.SomeType的方式在类型位置使用。这与 deep-dive.md 中阐述的核心概念一脉相承。
理解模板背后的类型系统:值、类型与命名空间
module.d.ts 模板之所以能同时容纳函数、接口、常量与命名空间,是因为 TypeScript 声明文件中的名字可以有三种不同含义,deep-dive.md 对此有系统论述:
- 类型:通过
type别名、interface、class、enum或指向类型的import创建,只能出现在类型位置; - 值:通过
let/const/var、包含值的namespace、enum、class、function或指向值的import创建,能出现在表达式位置; - 命名空间:用于限定类型归属,如
let x: A.B.C中的C来自A.B命名空间。
一个名字可以同时承载多种含义。class C { }就同时创建了类型C(实例结构)与值C(构造函数);enum也有相似行为。在声明文件中,我们可以通过组合让一个名字既表示值又表示类型。例如 deep-dive.md 中的示例:
// foo.d.ts export var Bar: { a: Bar }; export interface Bar { count: number; }使用时即可解构并同时利用两种含义:
import { Bar } from './foo'; let x: Bar = Bar.a; // 左侧是类型,右侧是值 console.log(x.count);这正是module.d.ts模板把export function、export interface、export const、export namespace并列排布的根本原因——它们分别对应模块导出对象上的值成员、类型成员与嵌套命名空间,组合起来才能完整刻画真实库的 API 形态。
声明文件中的依赖处理
实际库往往依赖其他库,在编写模块声明文件时需要正确处理依赖。根据 library-structures.md 与 global-plugin.d.ts 模板 的"利用依赖"一节,规则如下:
- 依赖全局库:使用
/// <reference types="someLib" />指令; - 依赖普通模块:在声明文件顶部使用
import语句,如import * as moment from 'moment';; - 模块/UMD 库依赖 UMD 库:同样使用
import语句,不要用/// <reference指令声明对 UMD 库的依赖。
一个融合了导入依赖与导出声明的index.d.ts示例:
/// <reference types="someGlobalLib" /> import * as moment from 'moment'; export function formatTime(t: moment.Moment): string; export interface Options { locale?: string; precision: number; }实战:按模板编写一个完整声明文件
下面以手册中反复出现的super-greeter为例,将模板各段落到一个具体的普通模块场景中。假设库的 JavaScript 用法是:
var greeter = require('super-greeter'); greeter.greet('World'); greeter.LEVEL; // 模块属性 var opts = greeter.makeOptions(); // 返回配置对象则super-greeter/index.d.ts可以按模板组织为:
// Type definitions for super-greeter 2.3.0 // Project: https://example.com/super-greeter // Definitions by: Jane Doe <https://example.com/jane> /*~ 非 UMD 模块,删除 export as namespace 段 */ /*~ 模块方法 */ export function greet(name: string): string; /*~ 模块属性(只读) */ export const LEVEL: number; /*~ 模块暴露的类型 */ export interface GreetingOptions { language?: string; emoji?: boolean; } /*~ 返回配置对象的方法,嵌套类型放入 namespace */ export function makeOptions(): superGreeter.Options; export namespace superGreeter { export interface Options { level: number; prefix?: string; } }使用者即可获得完整类型支持:
import * as greeter from 'super-greeter'; const msg: string = greeter.greet('World'); const opts: greeter.superGreeter.Options = greeter.makeOptions();与其他模块模板的衔接
本模板描述的是"普通模块"形态。当模块的行为超出"导出普通值/类型"时,需要切换到对应的专门模板:
- 模块可被当作函数调用(
const y = x(42))→ 使用 module-function.d.ts。该模板用export = MyFunction导出函数对象,并用declare namespace MyFunction挂载返回类型与属性; - 模块可用
new构造(const y = new x('hello'))→ 使用 module-class.d.ts。该模板用export = MyClass导出类构造函数; - 模块导入后会修改其他模块→ 使用 module-plugin.d.ts。该模板先
import * as m from 'someModule',再declare module 'someModule'扩充原模块。
需要注意,module-function.d.ts与module-class.d.ts都采用了export =(导出整个对象而非具名导出),其模板注释特别提醒:ES6 模块无法直接导出可调用的类对象,此类文件应按 CommonJS 风格import x = require('[~THE MODULE~]')导入;或者在开启--allowSyntheticDefaultImports或--esModuleInterop编译选项后使用默认导入import x from '[~THE MODULE~]'。library-structures.md 的脚注进一步说明:ES6 模块加载器中顶层对象只能拥有属性、永远不能被调用,常见的解决手段是为可调用的对象定义default导出;若在tsconfig.json中启用了"esModuleInterop": true,TypeScript 会自动处理这一差异。相比之下,本模板使用标准具名导出,与 ES 模块互操作更直接。
常见误区与最佳实践
结合手册 deep-dive.md 与其他模板,使用module.d.ts时有几点值得注意:
- 删除不匹配的段落:非 UMD 模块务必删除
export as namespace;模块没有嵌套成员时删除export namespace段。模板中的/*~注释块正是为此设计,逐段对照实际库的 API 决定保留或删除。 - 命名空间内使用
export:模块模板中的命名空间成员必须显式export,否则外部无法访问;这与全局模板declare namespace的内部语义一致。 - 类型与值的同名组合:可以利用接口与变量同名声明,让使用方一次导入同时获得类型与值,但要注意两者是独立的声明,互不约束。
- 避免顶层全局类型:library-structures.md 的"防止命名冲突"脚注建议:类型应放在模块/命名空间内部,而不是散落在全局作用域,以免多声明文件工程中出现难以解决的命名冲突。
小结
module.d.ts是 TypeScript 声明文件模板体系中对普通模块最通用的一张模板,它以"函数 + 接口 + 常量 + 命名空间"四种导出形态覆盖了绝大多数非可调用、非可构造模块的 API 刻画需求,同时也是理解module-function.d.ts、module-class.d.ts、module-plugin.d.ts的基础。编写时只需记住三条主线:识别库是否为模块化代码库 → 按模板逐段填写并删除不匹配段落 → 处理好依赖导入,即可产出结构规范、可发布的声明文件。相关模板全家桶可在 templates 目录 中按需查阅,模块与全局库的完整识别方法详见 library-structures.md,类型与值组合的深入原理可参考 deep-dive.md。
- 文档
- 教程
【免费下载链接】TypeScript
TypeScript 使用手册(中文版)翻译。http://www.typescriptlang.org
相关推荐
企业级客服机器人语义理解实践指南:如何利用GTE-large-zh提升智能客服效果
企业级客服机器人语义理解实践指南:如何利用GTE large zh提升智能客服效果 在当今数字化时代, GTE large zh 作为阿里巴巴达摩院开发的高性能
文档教程OrcaSlicer 自适应床网快速指南:只探测打印区域,告别整床调平
OrcaSlicer 自适应床网快速指南:只探测打印区域,告别整床调平 只打印一个 30mm 的小零件,却要为整张 220×220 的床探测二十几个点?Orca
文档教程TypeScript 模块插件声明文件编写指南:module-plugin.d.ts 模板深度解析
TypeScript 模块插件声明文件编写指南:module plugin.d.ts 模板深度解析 本指南围绕 TypeScript 中文手册声明文件章节中的
文档教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考