先记住这个答案
.d.ts 文件描述已有模块或全局环境的公开类型,例如函数参数、返回值、类成员和对象结构,让 TypeScript 在没有实现源码时也能检查调用。它不会实现函数、安装依赖、创建全局变量或校验运行时数据。声明可以随 JavaScript 包提供,也可以由类型包或项目自己的补充文件提供。维护声明时必须对照实际导出方式和行为,不能为了消除报错而编造不存在的成员;对自己用 TypeScript 编写的库,通常优先从实现生成公开声明,减少人工双写的偏差。
- 声明提供编译期契约,运行时值来自真实实现
- 参数和导出形式必须与实际库保持一致
- 手写声明适合补充缺失契约,不能用来修复运行时错误
没有源码也可以检查调用是否合理
消费者安装一个 JavaScript 工具包时,编译器未必分析包内全部实现。公开声明可以给出稳定边界,例如解析函数接收字符串,成功后返回带名称和标识的对象。调用者因此能获得参数检查、自动补全和返回值信息。
下面声明一个具名函数和它的返回接口。它没有函数体,不能解释内部如何解析,也没有承诺任何字符串都一定成功。是否抛错、是否接受空白、字段如何归一化仍需要真实实现与 API 文档明确,并由相应测试验证。
// index.d.ts
export interface ParsedUser {
id: number;
name: string;
}
export declare function parseUser(text: string): ParsedUser;消费者可以按 ParsedUser 使用返回值,但这些类型在运行时不会自动校验 JSON。若 JavaScript 实现返回错误字段,声明不会替它修正;若实现没有该导出,运行时仍然会失败。
先确认缺的是类型还是实际模块
报错找不到模块时,可能是包未安装、路径错误、exports 限制或类型缺失。只有最后一类才适合通过补声明解决。给根本不存在的包写环境模块声明,可能让类型检查安静下来,却把问题推迟到打包或启动时。
如果包已有官方类型,应先核对实际版本和模块解析配置;第三方类型包也需要与运行库匹配。旧声明可能漏掉新参数,新声明也可能承诺旧运行库没有的 API。不要把编辑器提示出现当作整个依赖安装与运行链路已经正确。
选择生成声明还是维护手写契约
自己的 TypeScript 库通常通过 declaration 从实现生成 .d.ts,让参数修改可以同步进入公开产物。但自动生成仍可能暴露不稳定内部类型,需要审查最终声明与消费测试。生成文件并不自动意味着设计出了合适的公共 API。
为无类型 JavaScript 库补声明时,可以先覆盖实际使用到的可靠接口,再逐步扩展。每个重载和可选字段都应该有行为依据;不确定输入可以保留 unknown 并在边界解析,不必一开始就用宽 any 宣称所有调用都合法。
容易答错的地方
- 声明了函数就能直接在运行时调用它
- 声明文件不会产生实现或加载脚本;消费者仍需要正确安装、导入或初始化真实模块,缺少运行时值时调用照样失败。
- 为了编译通过可以把返回值写成理想结构
- 声明应描述事实,错误承诺会让调用者在静态检查通过后遇到缺字段或错误类型;应修复实现、解析边界或声明本身。
面试官还会怎么问?
声明文件能包含接口和类型别名吗?
可以,它们用于描述公开契约和辅助类型;但声明中出现的运行时值必须在其他地方真实存在,不能依靠类型声明创建对象。
什么时候不需要自己写 .d.ts?
库已经提供匹配的类型,或自己的实现可以可靠生成声明时,通常直接使用现有产物;只有确实缺失或需要扩展契约时再补充。
给返回值写了精确类型还需要运行时解析吗?
对于外部数据仍需要。类型检查假设声明可信,网络响应和用户输入不会因为函数签名精确就自动符合这个结构。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。