Skip to content

声明文件

概述

声明文件(Declaration Files,.d.ts)是 TypeScript 中用于描述 JavaScript 代码类型信息的文件。它们不包含实际的实现代码,只包含类型定义,用于:

  • 为现有的 JavaScript 库提供类型支持
  • 描述第三方库的 API
  • 为没有类型定义的代码添加类型信息
  • 在 TypeScript 项目中使用纯 JavaScript 代码

声明文件让 TypeScript 编译器能够理解 JavaScript 代码的类型,提供完整的类型检查和智能提示功能。

声明文件的作用

为什么需要声明文件?

TypeScript 是 JavaScript 的超集,但很多现有的 JavaScript 库并没有 TypeScript 类型定义。声明文件充当了"桥梁"的作用:

typescript
// 假设有一个 JavaScript 库 utils.js
// 没有类型定义时,TypeScript 无法知道其类型
import { formatDate } from './utils'; // ❌ 类型错误

// 有了声明文件 utils.d.ts 后
import { formatDate } from './utils'; // ✅ 类型安全
const result: string = formatDate(new Date()); // 有完整的类型提示

声明文件的类型

  1. 全局声明文件:描述全局变量、函数、类型
  2. 模块声明文件:描述模块的导出内容
  3. 类型声明包:通过 @types/* 包提供的第三方库类型定义

基本语法

声明变量

typescript
// global.d.ts
declare const VERSION: string;
declare const API_URL: string;

// 使用
console.log(VERSION); // ✅ 类型为 string
console.log(API_URL); // ✅ 类型为 string

声明函数

typescript
// utils.d.ts
declare function formatDate(date: Date): string;
declare function parseDate(dateString: string): Date;

// 使用
const formatted = formatDate(new Date()); // ✅ 返回 string
const parsed = parseDate('2024-01-01'); // ✅ 返回 Date

声明类

typescript
// user.d.ts
declare class User {
  name: string;
  age: number;
  constructor(name: string, age: number);
  greet(): string;
}

// 使用
const user = new User('John', 30); // ✅ 类型安全
console.log(user.greet()); // ✅ 返回 string

声明接口和类型

typescript
// types.d.ts
declare interface User {
  id: number;
  name: string;
  email: string;
}

declare type UserStatus = 'active' | 'inactive' | 'pending';

// 使用
const user: User = {
  id: 1,
  name: 'John',
  email: 'john@example.com'
}; // ✅ 类型检查

模块声明

声明模块导出

typescript
// my-library.d.ts
declare module 'my-library' {
  export function calculateSum(a: number, b: number): number;
  export interface Config {
    apiKey: string;
    timeout: number;
  }
  export default class Calculator {
    add(a: number, b: number): number;
  }
}

// 使用
import Calculator, { calculateSum, Config } from 'my-library';
const result = calculateSum(1, 2); // ✅ 类型安全

声明命名空间

typescript
// utils.d.ts
declare namespace Utils {
  export function formatDate(date: Date): string;
  export function formatCurrency(amount: number): string;
  export namespace DateUtils {
    export function isValid(date: Date): boolean;
  }
}

// 使用
const formatted = Utils.formatDate(new Date());
const isValid = Utils.DateUtils.isValid(new Date());

全局声明

声明全局变量

typescript
// global.d.ts
declare global {
  interface Window {
    myCustomProperty: string;
    myCustomMethod(): void;
  }
  
  const __DEV__: boolean;
  const __VERSION__: string;
}

// 使用
window.myCustomProperty = 'value'; // ✅ 类型安全
window.myCustomMethod(); // ✅ 类型安全
console.log(__DEV__); // ✅ 类型为 boolean

扩展现有类型

typescript
// string-extensions.d.ts
declare global {
  interface String {
    capitalize(): string;
    reverse(): string;
  }
}

export {}; // 使文件成为模块

// 使用
const str = 'hello';
const capitalized = str.capitalize(); // ✅ 类型安全
const reversed = str.reverse(); // ✅ 类型安全

实际应用示例

示例 1:为 JavaScript 库创建声明文件

假设有一个 JavaScript 工具库 calculator.js

javascript
// calculator.js
export function add(a, b) {
  return a + b;
}

export function multiply(a, b) {
  return a * b;
}

export class Calculator {
  constructor(initialValue = 0) {
    this.value = initialValue;
  }
  
  add(n) {
    this.value += n;
    return this;
  }
  
  getValue() {
    return this.value;
  }
}

创建对应的声明文件:

typescript
// calculator.d.ts
export function add(a: number, b: number): number;
export function multiply(a: number, b: number): number;

export class Calculator {
  private value: number;
  constructor(initialValue?: number);
  add(n: number): this;
  getValue(): number;
}

使用:

typescript
import { add, multiply, Calculator } from './calculator';

const sum = add(1, 2); // ✅ 类型为 number
const product = multiply(3, 4); // ✅ 类型为 number

const calc = new Calculator(10);
const result = calc.add(5).getValue(); // ✅ 类型为 number

示例 2:为第三方库创建类型定义

为没有类型定义的第三方库创建声明文件:

typescript
// vendor-library.d.ts
declare module 'vendor-library' {
  export interface Options {
    apiKey: string;
    timeout?: number;
    retries?: number;
  }
  
  export class Client {
    constructor(options: Options);
    request(url: string): Promise<Response>;
    get<T>(url: string): Promise<T>;
    post<T>(url: string, data: unknown): Promise<T>;
  }
  
  export function createClient(options: Options): Client;
}

// 使用
import { createClient, Client } from 'vendor-library';

const client = createClient({
  apiKey: 'your-api-key',
  timeout: 5000
});

const data = await client.get<User[]>('/api/users'); // ✅ 类型安全

示例 3:声明全局配置对象

typescript
// config.d.ts
declare const AppConfig: {
  readonly apiUrl: string;
  readonly version: string;
  readonly environment: 'development' | 'production';
  readonly features: {
    readonly enableAnalytics: boolean;
    readonly enableDebug: boolean;
  };
};

// 使用
console.log(AppConfig.apiUrl); // ✅ 类型安全
console.log(AppConfig.features.enableAnalytics); // ✅ 类型安全

类型声明包(@types)

使用现有的类型声明包

许多流行的 JavaScript 库都有官方的类型定义包,通过 @types/* 提供:

bash
# 安装类型定义包
npm install --save-dev @types/node
npm install --save-dev @types/lodash
npm install --save-dev @types/react
typescript
// 安装后可以直接使用
import * as _ from 'lodash';
import { readFile } from 'fs/promises';
import React from 'react';

// 所有导入都有完整的类型支持
const numbers = _.range(1, 10); // ✅ 类型安全
const content = await readFile('file.txt', 'utf-8'); // ✅ 类型安全

查找类型声明包

TypeSearch 上搜索库的类型定义包。

声明合并

接口声明合并

typescript
// user.d.ts
interface User {
  name: string;
  age: number;
}

// user-extended.d.ts
interface User {
  email: string; // 合并到 User 接口
}

// 使用
const user: User = {
  name: 'John',
  age: 30,
  email: 'john@example.com' // ✅ 包含所有属性
};

命名空间声明合并

typescript
// utils.d.ts
namespace Utils {
  export function formatDate(date: Date): string;
}

// utils-extended.d.ts
namespace Utils {
  export function formatCurrency(amount: number): string;
}

// 使用
Utils.formatDate(new Date()); // ✅ 可用
Utils.formatCurrency(100); // ✅ 可用

三斜线指令

三斜线指令用于声明文件之间的依赖关系:

typescript
// types.d.ts
/// <reference types="node" />
/// <reference path="./user.d.ts" />
/// <reference lib="es2015" />

// 现在可以使用 Node.js 类型和 user.d.ts 中的类型
import { readFile } from 'fs/promises';
import type { User } from './user';

常用三斜线指令

  • /// <reference types="..." />:引用类型包
  • /// <reference path="..." />:引用其他声明文件
  • /// <reference lib="..." />:引用内置库类型

自动生成声明文件

使用 tsc 生成声明文件

tsconfig.json 中配置:

json
{
  "compilerOptions": {
    "declaration": true,
    "declarationDir": "./types",
    "declarationMap": true
  }
}

编译时会自动生成 .d.ts 文件:

typescript
// src/utils.ts
export function formatDate(date: Date): string {
  return date.toISOString();
}

export interface User {
  name: string;
  age: number;
}

编译后生成:

typescript
// types/utils.d.ts
export declare function formatDate(date: Date): string;
export interface User {
  name: string;
  age: number;
}

常见场景和最佳实践

场景 1:为项目添加全局类型

typescript
// types/global.d.ts
declare global {
  interface Window {
    gtag?: (...args: unknown[]) => void;
  }
  
  namespace NodeJS {
    interface ProcessEnv {
      readonly NODE_ENV: 'development' | 'production' | 'test';
      readonly API_URL: string;
      readonly VERSION: string;
    }
  }
}

export {};

场景 2:为 Webpack 环境变量添加类型

typescript
// webpack-env.d.ts
declare namespace NodeJS {
  interface ProcessEnv {
    readonly NODE_ENV: 'development' | 'production';
    readonly REACT_APP_API_URL: string;
  }
}

场景 3:扩展第三方库类型

typescript
// express-extensions.d.ts
import 'express';

declare module 'express-serve-static-core' {
  interface Request {
    user?: {
      id: number;
      name: string;
    };
  }
}

// 使用
app.get('/api/user', (req, res) => {
  if (req.user) {
    console.log(req.user.id); // ✅ 类型安全
  }
});

注意事项

重要提示

  1. 声明文件不包含实现:声明文件只包含类型定义,不包含实际的 JavaScript 代码。

  2. 文件扩展名:声明文件必须使用 .d.ts 扩展名,TypeScript 才能识别。

  3. 模块声明:如果要扩展全局类型,需要在文件末尾添加 export {} 使文件成为模块。

  4. 类型准确性:确保声明文件中的类型定义与实际 JavaScript 代码一致,否则可能导致运行时错误。

  5. 避免重复声明:不要重复声明已存在的类型,使用声明合并来扩展类型。

  6. 三斜线指令顺序:三斜线指令必须放在文件顶部,且顺序很重要。

最佳实践

  1. 使用 @types 包:优先使用官方或社区维护的 @types/* 包,而不是自己创建声明文件。

  2. 集中管理:将项目相关的声明文件放在 types/@types/ 目录中,便于管理。

  3. 命名规范:声明文件命名应与对应的 JavaScript 文件或模块名保持一致。

  4. 版本匹配:确保类型定义包的版本与 JavaScript 库的版本兼容。

  5. 文档化:在声明文件中添加 JSDoc 注释,提供更好的 IDE 提示。

  6. 测试类型:创建声明文件后,在实际代码中使用并验证类型是否正确。

  7. 使用 declare global:扩展全局类型时,使用 declare global 块,并在文件末尾添加 export {}

调试声明文件

检查声明文件是否正确

typescript
// 在 TypeScript 文件中使用
import { someFunction } from './some-module';

// 如果类型不正确,TypeScript 会报错
const result = someFunction('test'); // 检查类型提示是否正确

使用类型检查命令

bash
# 只进行类型检查,不生成文件
tsc --noEmit

# 检查特定文件
tsc --noEmit src/index.ts

相关链接

基于 VitePress 构建