news 2026/9/28 6:20:52

TypeScript 声明文件模块模板 module.d.ts:为模块化代码库编写类型定义的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TypeScript 声明文件模块模板 module.d.ts:为模块化代码库编写类型定义的完整指南
  • 文档
  • 教程

【免费下载链接】TypeScript

TypeScript 使用手册(中文版)翻译。http://www.typescriptlang.org

项目地址:https://gitcode.com/gh_mirrors/typ/TypeScript
点击查看免费下载

在 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); // number

7. 子命名空间: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时有几点值得注意:

  1. 删除不匹配的段落:非 UMD 模块务必删除export as namespace;模块没有嵌套成员时删除export namespace段。模板中的/*~注释块正是为此设计,逐段对照实际库的 API 决定保留或删除。
  2. 命名空间内使用export:模块模板中的命名空间成员必须显式export,否则外部无法访问;这与全局模板declare namespace的内部语义一致。
  3. 类型与值的同名组合:可以利用接口与变量同名声明,让使用方一次导入同时获得类型与值,但要注意两者是独立的声明,互不约束。
  4. 避免顶层全局类型: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

项目地址:https://gitcode.com/gh_mirrors/typ/TypeScript
点击查看免费下载

相关推荐

上一篇:MiniRust 开源项目教程
下一篇:CAI 多 Agent 交接提示词扩展解析:用 RECOMMENDED_PROMPT_PREFIX 让 Handoff 协作更稳定

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 6:18:55

20种机器学习算法Python代码包:从跑通到避坑的完整指南

简介&#xff1a;这份资源面向机器学习入门与进阶学习者&#xff0c;系统整理了20种常见算法的Python实现&#xff0c;覆盖线性回归、逻辑回归、BP神经网络、SVM支持向量机、K-Means聚类、PCA主成分分析以及异常检测等经典模型&#xff0c;适合希望从理论走向动手实践、需要可运…

作者头像 李华
网站建设 2026/9/28 6:18:27

CCC认证全流程拆解:申请、工厂检查、费用周期与合规价值

聊到产品认证&#xff0c;很多做硬件和消费电子的朋友可能都听说过“CCC”三个字母。我这些年经手过不少CCC项目的申请、审厂和整改&#xff0c;也亲眼见过产品因为证书问题被渠道平台直接下架的情况。说句实在话&#xff0c;CCC认证在国内市场就像产品的“入场券”&#xff0c…

作者头像 李华
网站建设 2026/9/28 6:18:26

分布式实时计算核心解析:从流处理原理到Flink实战避坑

1. 分布式计算的“实时”究竟是什么&#xff1a;先搞清楚批处理和流处理的本质差异这几年做大数据方向的技术分享&#xff0c;被问得最多的一个问题不是“Flink和Spark哪个好”&#xff0c;而是“你们说的实时到底是指多快”。有人在简历里写“熟练掌握实时计算”&#xff0c;但…

作者头像 李华
网站建设 2026/9/28 6:18:00

电力系统多产消者非合作博弈能量共享的分布式优化与MATLAB实现

看到【电力系统】基于分布式优化的多产消者非合作博弈能量共享附matlab代码这个标题&#xff0c;很多人的第一反应是&#xff1a;四个术语叠在一起&#xff0c;怕不是又一个把概念拼起来就跑的仿真水论文。我最早拿到这个问题的时候也是这么想的&#xff0c;直到真正动手把模型…

作者头像 李华
网站建设 2026/9/28 6:17:33

Flink窗口实战:滑动、会话、全局窗口机制详解

说实话&#xff0c;很多人对Flink窗口的理解停留在timeWindow(Time.seconds(10))这种最基础的滚动窗口上。一旦遇到"统计最近5分钟的交易量&#xff0c;每30秒刷新一次"这种需求&#xff0c;就开始纠结&#xff1b;再遇上"用户连续操作超过2分钟没动作&#xff…

作者头像 李华
网站建设 2026/9/28 6:17:28

基于CNN的垃圾识别分类系统:从数据集到部署的完整实战

简介&#xff1a;这份资源是面向高校学生与深度学习入门者的垃圾识别分类课程设计完整项目&#xff0c;基于卷积神经网络实现图像分类&#xff0c;可直接用于期末大作业或课程设计答辩。压缩包共约2000个文件&#xff0c;以1196张jpg与789张jpeg图像构成训练与测试数据集&#…

作者头像 李华