声明文件
概述
声明文件(Declaration Files,.d.ts)是 TypeScript 中用于描述 JavaScript 代码类型信息的文件。它们不包含实际的实现代码,只包含类型定义,用于:
- 为现有的 JavaScript 库提供类型支持
- 描述第三方库的 API
- 为没有类型定义的代码添加类型信息
- 在 TypeScript 项目中使用纯 JavaScript 代码
声明文件让 TypeScript 编译器能够理解 JavaScript 代码的类型,提供完整的类型检查和智能提示功能。
声明文件的作用
为什么需要声明文件?
TypeScript 是 JavaScript 的超集,但很多现有的 JavaScript 库并没有 TypeScript 类型定义。声明文件充当了"桥梁"的作用:
// 假设有一个 JavaScript 库 utils.js
// 没有类型定义时,TypeScript 无法知道其类型
import { formatDate } from './utils'; // ❌ 类型错误
// 有了声明文件 utils.d.ts 后
import { formatDate } from './utils'; // ✅ 类型安全
const result: string = formatDate(new Date()); // 有完整的类型提示声明文件的类型
- 全局声明文件:描述全局变量、函数、类型
- 模块声明文件:描述模块的导出内容
- 类型声明包:通过
@types/*包提供的第三方库类型定义
基本语法
声明变量
// global.d.ts
declare const VERSION: string;
declare const API_URL: string;
// 使用
console.log(VERSION); // ✅ 类型为 string
console.log(API_URL); // ✅ 类型为 string声明函数
// 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声明类
// 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声明接口和类型
// 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'
}; // ✅ 类型检查模块声明
声明模块导出
// 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); // ✅ 类型安全声明命名空间
// 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());全局声明
声明全局变量
// 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扩展现有类型
// 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:
// 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;
}
}创建对应的声明文件:
// 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;
}使用:
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:为第三方库创建类型定义
为没有类型定义的第三方库创建声明文件:
// 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:声明全局配置对象
// 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/* 提供:
# 安装类型定义包
npm install --save-dev @types/node
npm install --save-dev @types/lodash
npm install --save-dev @types/react// 安装后可以直接使用
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 上搜索库的类型定义包。
声明合并
接口声明合并
// 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' // ✅ 包含所有属性
};命名空间声明合并
// 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); // ✅ 可用三斜线指令
三斜线指令用于声明文件之间的依赖关系:
// 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 中配置:
{
"compilerOptions": {
"declaration": true,
"declarationDir": "./types",
"declarationMap": true
}
}编译时会自动生成 .d.ts 文件:
// src/utils.ts
export function formatDate(date: Date): string {
return date.toISOString();
}
export interface User {
name: string;
age: number;
}编译后生成:
// types/utils.d.ts
export declare function formatDate(date: Date): string;
export interface User {
name: string;
age: number;
}常见场景和最佳实践
场景 1:为项目添加全局类型
// 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 环境变量添加类型
// webpack-env.d.ts
declare namespace NodeJS {
interface ProcessEnv {
readonly NODE_ENV: 'development' | 'production';
readonly REACT_APP_API_URL: string;
}
}场景 3:扩展第三方库类型
// 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); // ✅ 类型安全
}
});注意事项
重要提示
声明文件不包含实现:声明文件只包含类型定义,不包含实际的 JavaScript 代码。
文件扩展名:声明文件必须使用
.d.ts扩展名,TypeScript 才能识别。模块声明:如果要扩展全局类型,需要在文件末尾添加
export {}使文件成为模块。类型准确性:确保声明文件中的类型定义与实际 JavaScript 代码一致,否则可能导致运行时错误。
避免重复声明:不要重复声明已存在的类型,使用声明合并来扩展类型。
三斜线指令顺序:三斜线指令必须放在文件顶部,且顺序很重要。
最佳实践
使用 @types 包:优先使用官方或社区维护的
@types/*包,而不是自己创建声明文件。集中管理:将项目相关的声明文件放在
types/或@types/目录中,便于管理。命名规范:声明文件命名应与对应的 JavaScript 文件或模块名保持一致。
版本匹配:确保类型定义包的版本与 JavaScript 库的版本兼容。
文档化:在声明文件中添加 JSDoc 注释,提供更好的 IDE 提示。
测试类型:创建声明文件后,在实际代码中使用并验证类型是否正确。
使用 declare global:扩展全局类型时,使用
declare global块,并在文件末尾添加export {}。
调试声明文件
检查声明文件是否正确
// 在 TypeScript 文件中使用
import { someFunction } from './some-module';
// 如果类型不正确,TypeScript 会报错
const result = someFunction('test'); // 检查类型提示是否正确使用类型检查命令
# 只进行类型检查,不生成文件
tsc --noEmit
# 检查特定文件
tsc --noEmit src/index.ts相关链接
- tsconfig.json 配置文件 - 了解如何配置声明文件生成
- 编译选项详解 - 了解
declaration相关选项 - 模块系统 - 了解模块声明的工作原理
- TypeScript 官方文档 - 声明文件
- TypeScript 官方文档 - 声明文件最佳实践
- DefinitelyTyped - 社区维护的类型定义仓库