模块、命名空间与 .d.ts 声明文件
本节目标
- 理解 ESM
import/export在 TS 里的类型处理 - 搞懂「声明文件
.d.ts」是干嘛的 - 会为一个没有类型的 JS 库写最小声明
TS 项目里 import 时,TS 会去「找类型」:优先找 .ts,没找到就找同名 .d.ts。
// 运行环境:Node.js 16+ + typescript;类型检查 npx tsc --noEmit ts-l14.ts
// 正常导入带类型的模块
import { sum } from './math' // 若 math.ts 导出 sum:number,这里就有提示
// 给「没有类型的第三方 JS」补一个声明文件:math.d.ts
// 内容示例(放在被导入文件同目录或 @types 下):
// declare function legacyAdd(a: number, b: number): number
// declare module 'legacy-lib' { export function legacyAdd(a: number, b: number): number }.d.ts 是「只有类型、没有实现」的文件,专门描述「某个 JS 长什么样」,让 TS 能给无类型库补上提示。
// 自己写的最简声明:告诉 TS 全局有个 formatDate 函数
declare function formatDate(ts: number): string
// 之后任何地方调用 formatDate 都有类型检查(实现由运行时 JS 提供)模块 vs 命名空间:现代 TS 一律用 ES Module(import/export);namespace 是老写法,新项目几乎不用,别碰。
名词解释
声明文件(.d.ts):一种「只写类型、不写逻辑」的特殊文件。它像是给某个 JS 库的「说明书」:告诉 TS「这个库导出了什么、各长啥类型」。有了它,你 import 一个纯 JS 库也能享受类型提示。社区把常用库的声明集中放在 @types/* 包里(如 @types/lodash)。
模块(Module / ESM):用 import/export 把代码拆成「可复用、作用域隔离」的小文件。TS 原生支持 ES Module,import 时自动解析类型。它是现代前端组织的基石,替代了老式的「全局变量」和 namespace。
课后练习
练习 1:一个 JS 库只导出 function slugify(s){...},怎么给它写 .d.ts?
答案:新建
slugify.d.ts,写declare function slugify(s: string): string; export = slugify;(或export default function slugify(s: string): string)。之后 TS 就能识别它。
练习 2:TS 导入一个模块时,类型解析的优先级是怎样的?
答案:先找同名的
.ts实现文件;找不到再找同名.d.ts声明;若是第三方包,还会查node_modules/@types/<包名>和包内types字段指向的.d.ts。都不存在就报「找不到模块/类型」。
总结
我的看法:.d.ts 是 TS 生态能「兼容整个 JS 世界」的秘密武器——它让你既能用海量无类型 JS 库,又不丢了类型安全。新手最容易怕它,其实最小声明只要 declare 几行就能救命。关于 namespace,我的建议是新代码一律不写:它是 ES Module 普及前的过渡方案,现在用 import/export 既标准又清爽,工具链支持也最好。最后提醒:装第三方库时顺手 npm i -D @types/xxx 几乎成了肌肉记忆——类型提示的体验差距,用过就回不去。