第 108 章:声明文件(.d.ts)
学习目标
- 理解 .d.ts 声明文件的作用与结构
- 学会为 JS 库编写类型声明
- 掌握全局类型扩展
- 理解 DefinitelyTyped 与 @types 的工作原理
一、什么是声明文件
.d.ts 文件是纯类型声明文件,只包含类型,不包含实现。
1.1 文件作用
typescript
// types.d.ts
declare const version: string;
declare function greet(name: string): string;
declare class User {
constructor(name: string);
getName(): string;
}核心作用:
- 为 JS 库提供类型
- 扩展全局类型
- 声明环境变量
- 扩展第三方模块
二、声明文件基础
2.1 declare 关键字
typescript
// 声明全局变量
declare const VERSION: string;
// 声明全局函数
declare function log(message: string): void;
// 声明全局类
declare class Animal {
constructor(name: string);
speak(): void;
}
// 声明全局命名空间
declare namespace MyLib {
function foo(): void;
}2.2 declare module
typescript
// 声明一个模块
declare module 'my-lib' {
export function foo(): void;
export const VERSION: string;
export interface Options {
debug?: boolean;
}
}
// 引入时有类型
import { foo, VERSION, Options } from 'my-lib';
foo();2.3 declare global
typescript
// 在模块文件里扩展全局
declare global {
interface Window {
myApp: {
version: string;
track(event: string): void;
};
}
const __APP_VERSION__: string;
}
export {}; // 必须有 export/import 才能成为模块
// 使用
window.myApp.track('click');
console.log(__APP_VERSION__);三、常用声明模式
3.1 资源文件声明
typescript
// CSS Modules
declare module '*.module.css' {
const classes: { readonly [key: string]: string };
export default classes;
}
// 普通 CSS
declare module '*.css';
// 图片
declare module '*.png' {
const src: string;
export default src;
}
declare module '*.svg' {
const src: string;
export default src;
}
declare module '*.jpg' {
const src: string;
export default src;
}3.2 JSON 模块
typescript
declare module '*.json' {
const value: any;
export default value;
}3.3 全局变量
typescript
// globals.d.ts
declare const __DEV__: boolean;
declare const __API_URL__: string;
declare const __BUILD_TIME__: string;
// 第三方注入的全局
declare const ga: (command: string, ...args: any[]) => void;3.4 扩展已有类型
typescript
// 扩展 Window
declare global {
interface Window {
dataLayer: any[];
gtag: (...args: any[]) => void;
}
}
// 使用
window.dataLayer.push('event');
window.gtag('config', 'GA_ID');3.5 扩展第三方模块
typescript
// 给 express 的 Request 加 user 字段
declare global {
namespace Express {
interface Request {
user?: {
id: number;
name: string;
};
}
}
}四、为 JS 库编写声明
4.1 简单示例
假设有一个 JS 库 my-utils.js:
javascript
// my-utils.js
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
export const VERSION = '1.0.0';对应的声明文件 my-utils.d.ts:
typescript
export function add(a: number, b: number): number;
export function multiply(a: number, b: number): number;
export const VERSION: string;4.2 复杂库
javascript
// complex-lib.js
export default class Validator {
constructor(options) {
this.options = options;
}
validate(value) {
return true;
}
}
export function createValidator(options) {
return new Validator(options);
}对应的声明:
typescript
// complex-lib.d.ts
export interface ValidatorOptions {
strict?: boolean;
allowNull?: boolean;
}
export default class Validator {
constructor(options: ValidatorOptions);
validate(value: unknown): boolean;
readonly options: ValidatorOptions;
}
export function createValidator(options: ValidatorOptions): Validator;4.3 命名空间导出
javascript
// chart.js
export class Chart {}
export const defaults = {};
export namespace utils {
export function random() {}
}声明文件:
typescript
// chart.d.ts
export class Chart {
constructor(config: any);
}
export const defaults: {
color: string;
size: number;
};
export namespace utils {
export function random(): number;
}五、DefinitelyTyped 与 @types
社区维护了大量第三方库的类型声明,放在 DefinitelyTyped 仓库。
5.1 安装 @types
bash
# 安装 React 的类型
pnpm add -D @types/react
# 安装 Node.js 类型
pnpm add -D @types/node
# 安装 lodash 类型
pnpm add -D @types/lodash5.2 自动加载
json
// tsconfig.json
{
"compilerOptions": {
"types": ["node", "jest"] // 显式指定要加载的 @types
}
}提示
没有 types 配置时,所有 @types/* 都会自动包含。配置 types 后只加载指定的。
5.3 找不到类型怎么办
bash
# 1. 库自带类型(看 package.json 的 "types" 字段)
# 2. 安装 @types/xxx
pnpm add -D @types/xxx
# 3. 自己写 .d.ts六、声明文件位置
6.1 与源码同目录
src/
├── my-lib.js
├── my-lib.d.ts # 同目录声明
└── index.ts6.2 types 目录
src/
├── types/
│ ├── globals.d.ts # 全局声明
│ ├── modules.d.ts # 模块声明
│ └── index.d.ts
└── index.ts6.3 根目录
project/
├── types/
│ └── index.d.ts
├── tsconfig.json
└── src/6.4 typeRoots
json
// tsconfig.json
{
"compilerOptions": {
"typeRoots": ["./node_modules/@types", "./src/types"]
}
}七、实战:企业级项目声明文件
7.1 项目结构
src/
├── types/
│ ├── globals.d.ts # 全局声明
│ ├── shims.d.ts # 模块占位
│ ├── api.d.ts # API 类型
│ └── index.d.ts # 统一导出
├── env.d.ts # 环境变量
└── ...7.2 globals.d.ts
typescript
// 全局类型扩展
declare global {
// Window 扩展
interface Window {
__APP_CONFIG__: {
apiUrl: string;
env: 'development' | 'production';
};
dataLayer: any[];
}
// 全局常量
const __BUILD_HASH__: string;
const __DEV__: boolean;
}
export {};7.3 shims.d.ts
typescript
// 模块占位声明
declare module '*.vue' {
import type { DefineComponent } from 'vue';
const component: DefineComponent<{}, {}, any>;
export default component;
}
declare module '*.svg' {
const src: string;
export default src;
}
declare module '*.module.css' {
const classes: { [key: string]: string };
export default classes;
}7.4 env.d.ts(Vite)
typescript
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL: string;
readonly VITE_APP_TITLE: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}八、模块声明 vs 全局声明
| 类型 | 范围 | 何时用 |
|---|---|---|
declare module 'x' | 仅 import 'x' 时 | 第三方模块类型 |
declare global | 全局可见 | Window、全局变量、扩展第三方 |
declare namespace | 全局可见 | 已废弃,避免用 |
选择建议
typescript
// ✅ 推荐:模块声明
declare module 'jquery' {
export function $(selector: string): any;
}
// ✅ 推荐:全局扩展(必须 declare global)
declare global {
interface Window {
myLib: any;
}
}
// ❌ 避免:全局污染
declare namespace global {
// ...
}九、声明文件实战技巧
9.1 让 JS 库支持 Tree-shaking
typescript
// my-lib.d.ts
export declare function foo(x: number): string;
export declare function bar(): void;9.2 条件类型导出
typescript
declare module 'env-conditional' {
if (typeof process !== 'undefined') {
export const env: 'node';
} else {
export const env: 'browser';
}
}9.3 默认导出和命名导出
typescript
declare module 'mixed-lib' {
// 命名导出
export function foo(): void;
// 默认导出
const defaultExport: { bar: () => void };
export default defaultExport;
}9.4 类型导出
typescript
declare module 'utils' {
export interface Config {
debug: boolean;
}
export type Status = 'ok' | 'error';
export function getStatus(): Status;
}十、常见错误
10.1 .d.ts 中写了实现
typescript
// ❌ 错误:.d.ts 不能有实现
export function foo() {
console.log('hello'); // ❌ .d.ts 不允许有函数体
}
// ✅ 正确:只声明签名
export function foo(): void;10.2 忘记 declare
typescript
// ❌ 在 .d.ts 中直接写 const 会被当成"实际值"
export const VERSION = '1.0.0'; // ❌ .d.ts 中这表示运行时存在
// ✅ 正确:用 declare
export declare const VERSION: string;10.3 类型冲突
typescript
// ❌ 不同 .d.ts 中重复声明同一全局类型
// globals1.d.ts
declare global {
interface Window {
foo: string;
}
}
// globals2.d.ts
declare global {
interface Window {
foo: number; // ❌ 类型冲突
}
}
// ✅ 解决:统一在一个地方扩展10.4 declare global 忘记 export {}
typescript
// ❌ 错误:文件没有 export/import,不是模块
declare global {
interface Window {
foo: string;
}
}
// 报错:Augmentations for the global scope can only be directly nested in external modules
// ✅ 正确:加 export {}
declare global {
interface Window {
foo: string;
}
}
export {};十一、发布带类型的库
11.1 package.json 配置
json
{
"name": "my-lib",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": ["dist"]
}11.2 三种发布方式
bash
# 方式 1:JS + 单独类型包
my-lib (JS) + @types/my-lib (DefinitelyTyped)
# 方式 2:内置类型(推荐)
my-lib (TS 源码 + 自动生成 .d.ts)
# 方式 3:随包提供类型
my-lib/dist/index.d.ts十二、本章小结
| 要点 | 关键 |
|---|---|
.d.ts | 纯类型声明,无实现 |
declare | 声明全局变量/函数/类 |
declare module | 声明模块 |
declare global | 在模块中扩展全局 |
@types/* | 社区维护的类型包 |
typeRoots | 配置类型根目录 |
| 文件位置 | types/、src/ 根目录 |
| 发布 | types 字段指向 .d.ts |
动手练习
- 写声明文件:为一个简单的 JS 函数库写 .d.ts
- 扩展 Window:在项目中扩展 Window,添加自定义全局方法
- shims 文件:配置 .vue、.svg、.css 模块的声明
- 环境变量:配置
ImportMetaEnv,让import.meta.env.VITE_XXX有类型提示
推荐阅读
- 📖 TypeScript Handbook - Declaration Files — 声明文件官方文档
- 📖 DefinitelyTyped — 社区类型仓库
- 🌐 dts-gen — 自动生成 .d.ts 工具
下一章:第 109 章:tsconfig 详解 →