.d.ts 文件只包含类型声明,没有实现。它存在的意义是:给没有类型的 JavaScript 代码提供类型。
第三方库的三种情况
1// 使用第三方类型:三种情况的处理
2
3// ---- 情况一:库自带类型 ----
4// 看 package.json 的 types / typings 字段
5// 例如 zod、axios 都自带类型,直接 import 即可
6
7// ---- 情况二:有 @types/xxx 包 ----
8// npm i -D @types/node
9// 然后就能用 Node 的全局类型
10const timer: NodeJS.Timeout = setTimeout(() => {}, 0);
11clearTimeout(timer);
12
13// Node 的类型
14const buf: Buffer = Buffer.from("hello");
15const platform: NodeJS.Platform = process.platform;
16
17// ---- 情况三:都没有,自己声明 ----
18// 见同目录的 ambient.d.ts
19// 注意:用 import type 只能拿类型,不能当值用
20import type { doSomething } from "some-legacy-lib";
21const typeOnly: typeof doSomething = (s: string) => s.length;
22
23// ---- 用 import type 导入纯类型 ----
24// verbatimModuleSyntax 下必须区分
25import type { Helper } from "./helper";
26
27const h: Helper = { run: () => "ok" };
28
29// ---- 常见:给全局对象加类型 ----
30// 见 global.d.ts
31
32// ---- 检查类型是否安装成功 ----
33// tsc --noEmit 会报 TS2688 如果找不到类型包
34// error TS2688: Cannot find type definition file for 'xxx'.
35
36// ---- skipLibCheck 的影响 ----
37// 开启后,.d.ts 文件里的类型错误不会被检查
38// 好处:编译快、避免第三方类型冲突
39// 代价:自己写的 .d.ts 里的错误也会被跳过
40
41console.log(buf.toString(), platform, h.run(), typeOnly("abc"), typeof timer);
1hello darwin ok 3 object
| 情况 | 处理方式 |
|---|---|
| 库自带类型 | 直接用(看 package.json 的 types 字段) |
有 @types/xxx 包 | npm i -D @types/xxx |
| 都没有 | 自己写 declare module |
查有没有类型包
1npm view @types/jquery version
安装类型包
1npm install --save-dev @types/jquery
装好后全局可用:
1const timer: NodeJS.Timeout = setTimeout(() => {}, 0);
2const buf: Buffer = Buffer.from("hello");
3const platform: NodeJS.Platform = process.platform;
如果类型包没装或版本不对,会报:
1error TS2688: Cannot find type definition file for 'node'.
自己写声明
1// 给没有类型的库写声明
2// 全局脚本文件(无 import/export)
3declare module "some-legacy-lib" {
4 export function doSomething(input: string): number;
5 export const VERSION: string;
6}
7
8// 通配模块:用于 import "./style.css" 这类
9declare module "*.css" {
10 const classes: Record<string, string>;
11 export default classes;
12}
13
14declare module "*.png" {
15 const src: string;
16 export default src;
17}
18
19// 声明全局常量(运行时由构建工具注入)
20declare const __BUILD_TIME__: string;
模块声明
1declare module "some-legacy-lib" {
2 export function doSomething(input: string): number;
3 export const VERSION: string;
4}
通配模块
用于 import "./style.css" 这类资源导入:
1declare module "*.css" {
2 const classes: Record<string, string>;
3 export default classes;
4}
5
6declare module "*.png" {
7 const src: string;
8 export default src;
9}
全局常量
1declare const __BUILD_TIME__: string;
这假设运行时由构建工具注入(Vite 的 define、webpack 的 DefinePlugin)。
文件位置规则
| 声明 | 写在哪种文件 |
|---|---|
declare module "xxx" | 全局脚本(无 import/export) |
declare global | 模块文件(有 import/export) |
declare const / declare function | 都可以 |
写错位置会报:
1error TS2664: Invalid module name in augmentation, module 'xxx' cannot be found.
这条消息很误导——它说"找不到模块",但真正的问题是在模块文件里写 declare module 会被当成模块扩充(augmentation),而扩充的目标模块必须已经存在。详见 声明合并。
推荐目录结构
1types/
2├── legacy-lib.d.ts # declare module "legacy-lib"
3├── assets.d.ts # declare module "*.css"
4└── global.d.ts # declare const / 全局接口扩展
确保 tsconfig.json 覆盖它:
1{ "include": ["src", "types"] }
扩展全局类型
1// 扩展全局接口(必须写在模块文件里,见 global-extend.ts)
2// 这里只演示非 DOM 的全局扩展
3declare namespace NodeJS {
4 interface ProcessEnv {
5 NODE_ENV?: "development" | "production";
6 API_URL?: string;
7 }
8}
常见的两种扩展:
1// 扩展 Node 的环境变量
2declare namespace NodeJS {
3 interface ProcessEnv {
4 NODE_ENV?: "development" | "production";
5 API_URL?: string;
6 }
7}
8
9// 扩展 Window(需要 DOM lib)
10declare global {
11 interface Window {
12 __APP_VERSION__: string;
13 }
14}
declare global 必须在模块文件里,通常配 export {}。
import type 与 verbatimModuleSyntax
开启 verbatimModuleSyntax 后,类型导入必须显式标记:
1import { SomeType } from "./other";
2// error TS1484: 'SomeType' is a type and must be imported using a type-only
3// import when 'verbatimModuleSyntax' is enabled.
4
5import type { SomeType } from "./other"; // ✓
这很重要,因为 esbuild/swc 这类单文件转译工具无法判断 SomeType 是类型还是值。标记清楚它们才能正确处理。
import type 导入的东西不能当值用1import type { doSomething } from "some-legacy-lib";
2
3doSomething("x");
4// error TS1361: 'doSomething' cannot be used as a value because it was
5// imported using 'import type'.
要用值就用普通 import。但那样运行时必须真的存在这个模块——declare module 只提供类型(见 类型擦除)。
skipLibCheck 的取舍
1{ "skipLibCheck": true }
开启的好处:
- 编译明显变快(不用检查所有
.d.ts) - 避免第三方类型定义冲突(不同库依赖不同版本的
@types/node时很常见)
代价:
- 你自己写的
.d.ts里的错误也会被跳过
所以建议:平时开着,写自己的声明文件时临时关掉验证一遍。
速查
| 需求 | 写法 |
|---|---|
| 给无类型库加类型 | declare module "xxx" |
| 资源导入 | declare module "*.css" |
| 全局常量 | declare const X: T |
| 扩展全局接口 | declare global(模块文件) |
| 扩展环境变量 | declare namespace NodeJS |
| 只导入类型 | import type { T } |