Skip to content
第 108 / 250 章前端⏱ 10 分钟阅读

第 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/lodash

5.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.ts

6.2 types 目录

src/
├── types/
│   ├── globals.d.ts   # 全局声明
│   ├── modules.d.ts   # 模块声明
│   └── index.d.ts
└── index.ts

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

动手练习

  1. 写声明文件:为一个简单的 JS 函数库写 .d.ts
  2. 扩展 Window:在项目中扩展 Window,添加自定义全局方法
  3. shims 文件:配置 .vue、.svg、.css 模块的声明
  4. 环境变量:配置 ImportMetaEnv,让 import.meta.env.VITE_XXX 有类型提示

推荐阅读


下一章第 109 章:tsconfig 详解

本站基于 VitePress 构建 · 由 Codebook 团队维护