.d.ts 与第三方类型

2026-09-09 00:00    #typescript  

.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 }

开启的好处:

代价:

所以建议:平时开着,写自己的声明文件时临时关掉验证一遍。

速查

需求写法
给无类型库加类型declare module "xxx"
资源导入declare module "*.css"
全局常量declare const X: T
扩展全局接口declare global(模块文件)
扩展环境变量declare namespace NodeJS
只导入类型import type { T }

相关