用户工具

站点工具


typescript:第十一章类型声明文件

第十一章 类型声明文件

类型声明文件(.d.ts)是TypeScript项目的重要组成部分,它描述了JavaScript库的类型信息,使TypeScript能够在使用JavaScript库时提供类型检查和智能提示。

11.1 声明文件基础

11.1.1 声明文件的作用

类型声明文件只包含类型信息,不包含实现: - 描述现有JavaScript代码的类型 - 为第三方库提供类型定义 - 扩展全局对象的类型

// mylib.d.ts - 声明文件示例
declare module "mylib" {
  export interface User {
    id: number;
    name: string;
  }
 
  export function createUser(name: string): User;
  export const version: string;
}

11.1.2 声明文件位置

TypeScript按以下顺序查找声明文件:

1. 内置类型:lib.d.ts等 2. package.json中的types字段:指定入口声明文件 3. @types包:DefinitelyTyped社区维护的类型 4. 项目中声明文件

  1. tsconfig.json中files/include指定的文件
  2. 与.js文件同名的.d.ts文件
  3. 项目根目录的.d.ts文件

11.2 创建声明文件

11.2.1 声明语句类型

// types.d.ts
 
// 声明变量
declare const VERSION: string;
declare let config: { apiUrl: string };
 
// 声明函数
declare function greet(name: string): string;
declare function process(callback: (result: string) => void): void;
 
// 声明类
declare class Animal {
  constructor(name: string);
  name: string;
  move(distance: number): void;
}
 
// 声明枚举
declare enum Direction {
  Up,
  Down,
  Left,
  Right
}
 
// 声明模块
export interface Config {
  debug: boolean;
}
 
// 声明命名空间
declare namespace MyLib {
  function doSomething(): void;
  const version: string;
}

11.2.2 为JavaScript库编写声明

假设有一个JavaScript库my-utils.js:

// my-utils.js
function add(a, b) {
  return a + b;
}
 
class Calculator {
  constructor() {
    this.result = 0;
  }
  multiply(x) {
    return this.result *= x;
  }
}
 
module.exports = { add, Calculator };

对应的声明文件:

// my-utils.d.ts
export function add(a: number, b: number): number;
 
export class Calculator {
  result: number;
  constructor();
  multiply(x: number): number;
}

11.2.3 UMD模块声明

// 支持CommonJS/AMD/全局变量
declare namespace MyLibrary {
  function doSomething(): void;
  const version: string;
}
 
export = MyLibrary;
export as namespace MyLibrary;

11.3 模块声明模式

11.3.1 全局库声明

用于直接通过script标签引入的库:

// jquery.d.ts
declare const $: JQueryStatic;
declare const jQuery: JQueryStatic;
 
interface JQueryStatic {
  (selector: string): JQuery;
  ajax(url: string, settings?: JQuery.AjaxSettings): JQuery.jqXHR;
  fn: any;
  extend(deep: boolean, target: any, ...objects: any[]): any;
}
 
interface JQuery {
  addClass(className: string): this;
  attr(attributeName: string): string | undefined;
  attr(attributeName: string, value: string | number): this;
  on(events: string, handler: (event: JQuery.Event) => void): this;
  off(events: string, handler?: (event: JQuery.Event) => void): this;
}
 
declare namespace JQuery {
  interface AjaxSettings {
    url?: string;
    method?: string;
    data?: any;
    success?: (data: any, textStatus: string, jqXHR: jqXHR) => void;
  }
 
  interface Event {
    type: string;
    target: Element;
    preventDefault(): void;
    stopPropagation(): void;
  }
 
  interface jqXHR {
    done(callback: (data: any, textStatus: string, jqXHR: jqXHR) => void): this;
    fail(callback: (jqXHR: jqXHR, textStatus: string, errorThrown: string) => void): this;
  }
}

11.3.2 模块库声明

// lodash.d.ts
declare module "lodash" {
  export function chunk<T>(array: T[], size: number): T[][];
  export function debounce<T extends (...args: any[]) => any>(
    func: T,
    wait?: number,
    options?: DebounceSettings
  ): T & { cancel(): void; flush(): ReturnType<T> };
 
  export function throttle<T extends (...args: any[]) => any>(
    func: T,
    wait?: number,
    options?: ThrottleSettings
  ): T & { cancel(): void; flush(): ReturnType<T> };
 
  export function cloneDeep<T>(value: T): T;
  export function merge<TObject, TSource>(
    object: TObject,
    source: TSource
  ): TObject & TSource;
 
  export function groupBy<T>(
    collection: T[],
    iteratee: ((value: T) => string) | string
  ): { [key: string]: T[] };
 
  interface DebounceSettings {
    leading?: boolean;
    trailing?: boolean;
    maxWait?: number;
  }
 
  interface ThrottleSettings {
    leading?: boolean;
    trailing?: boolean;
  }
}

11.3.3 插件扩展声明

为现有库添加方法:

// jquery-myplugin.d.ts
/// <reference types="jquery" />
 
declare global {
  interface JQuery {
    myPlugin(options?: MyPluginOptions): this;
    greenify(): this;
  }
 
  interface JQueryStatic {
    myPlugin: {
      defaults: MyPluginOptions;
      setDefaults(options: MyPluginOptions): void;
    };
  }
}
 
interface MyPluginOptions {
  color?: string;
  backgroundColor?: string;
}
 
export {};

11.4 高级声明技术

11.4.1 条件类型与推断

// 声明条件类型
type ElementType<T> = T extends (infer E)[] ? E : T;
 
// 声明映射类型
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};
 
type Partial<T> = {
  [P in keyof T]?: T[P];
};
 
type Required<T> = {
  [P in keyof T]-?: T[P];
};
 
type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};
 
type Record<K extends keyof any, T> = {
  [P in K]: T;
};

11.4.2 函数重载声明

// 复杂函数重载
declare function fetch(url: string): Promise<Response>;
declare function fetch(
  url: string,
  options: RequestInit
): Promise<Response>;
declare function fetch(
  input: RequestInfo,
  init?: RequestInit
): Promise<Response>;
 
// 基于返回类型的重载
declare function createElement(tagName: "a"): HTMLAnchorElement;
declare function createElement(tagName: "canvas"): HTMLCanvasElement;
declare function createElement(tagName: "div"): HTMLDivElement;
declare function createElement(tagName: "input"): HTMLInputElement;
declare function createElement(tagName: "span"): HTMLSpanElement;
declare function createElement(tagName: string): HTMLElement;

11.4.3 泛型约束声明

// 泛型接口
declare interface ApiResponse<T = any> {
  data: T;
  status: number;
  message: string;
}
 
declare interface PaginatedResponse<T> {
  items: T[];
  total: number;
  page: number;
  pageSize: number;
}
 
// 泛型函数约束
declare function find<T, K extends keyof T>(
  array: T[],
  key: K,
  value: T[K]
): T | undefined;
 
declare function sortBy<T, K extends keyof T>(
  array: T[],
  key: K
): T[];
 
declare function groupBy<T, K extends keyof T>(
  array: T[],
  key: K
): Map<T[K], T[]>;

11.5 发布类型定义

11.5.1 与npm包一起发布

在package.json中指定types字段:

{
  "name": "my-library",
  "version": "1.0.0",
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.js",
      "types": "./dist/index.d.ts"
    },
    "./package.json": "./package.json"
  },
  "files": [
    "dist",
    "src"
  ]
}

11.5.2 提交到DefinitelyTyped

对于没有内置类型的流行库,可以提交到@types组织:

1. Fork DefinitelyTyped/DefinitelyTyped 2. 在types目录创建包文件夹 3. 编写index.d.ts和测试文件 4. 提交Pull Request

结构:

types/my-library/
├── index.d.ts          # 主声明文件
├── my-library-tests.ts # 测试文件
├── tsconfig.json       # 配置
└── package.json        # {"private": true}

11.6 声明文件最佳实践

11.6.1 避免常见错误

// ❌ 错误:在声明文件中使用实现
export function add(a: number, b: number) {
  return a + b;
}
 
// ✅ 正确:只声明类型
export function add(a: number, b: number): number;
 
// ❌ 错误:导入使用require
import fs = require("fs");
 
// ✅ 正确:使用ES模块语法
import * as fs from "fs";
import type { Readable } from "stream";
 
// ❌ 错误:使用any
export function process(data: any): any;
 
// ✅ 正确:使用unknown或具体类型
export function process<T>(data: T): Processed<T>;

11.6.2 文档与注释

/**
 * 用户管理类
 * @example
 * ```ts
 * const userManager = new UserManager();
 * const user = await userManager.create({ name: "John" });
 * ```
 */
export class UserManager {
  /**
   * 创建新用户
   * @param data - 用户数据
   * @returns 创建的用户对象
   * @throws {ValidationError} 数据验证失败时抛出
   */
  create(data: CreateUserData): Promise<User>;
 
  /**
   * 根据ID查找用户
   * @param id - 用户ID
   * @returns 用户对象,不存在时返回null
   */
  findById(id: string): Promise<User | null>;
}
 
/** 创建用户的数据 */
export interface CreateUserData {
  /** 用户名,3-20个字符 */
  name: string;
  /** 邮箱地址 */
  email: string;
  /** 可选的个人简介 */
  bio?: string;
}

11.6.3 版本兼容性

// 使用ts版本标记
// TypeScript Version: 4.0
 
// 条件类型特性
// TypeScript Version: 2.8
 
// 模板字面量类型
// TypeScript Version: 4.1
 
// 在package.json中指定
{
  "name": "@types/mylib",
  "version": "1.0.0",
  "typescript": {
    "version": ">=4.0.0"
  }
}

11.7 实际案例

11.7.1 Express类型定义

// express 类型定义简化版
declare namespace Express {
  interface Request {
    body: any;
    params: { [key: string]: string };
    query: { [key: string]: string | string[] };
    headers: IncomingHttpHeaders;
    ip?: string;
    path: string;
    protocol: string;
    secure: boolean;
    xhr: boolean;
  }
 
  interface Response {
    status(code: number): this;
    json(body?: any): this;
    send(body?: any): this;
    redirect(url: string): this;
    render(view: string, options?: object): void;
  }
 
  interface NextFunction {
    (err?: any): void;
  }
 
  interface Application {
    use(...handlers: RequestHandler[]): this;
    get(path: string, ...handlers: RequestHandler[]): this;
    post(path: string, ...handlers: RequestHandler[]): this;
    listen(port: number, callback?: () => void): Server;
  }
}
 
type RequestHandler = (
  req: Express.Request,
  res: Express.Response,
  next: Express.NextFunction
) => void | Promise<void>;
 
declare function express(): Express.Application;
 
declare namespace express {
  export function json(): RequestHandler;
  export function urlencoded(options?: { extended?: boolean }): RequestHandler;
  export function static(root: string, options?: any): RequestHandler;
}
 
export = express;

11.7.2 数据库驱动类型

// 通用数据库驱动类型
export interface DatabaseConnection {
  query<T = any>(sql: string, params?: any[]): Promise<T[]>;
  execute(sql: string, params?: any[]): Promise<{ affectedRows: number }>;
  transaction<T>(callback: (trx: Transaction) => Promise<T>): Promise<T>;
  close(): Promise<void>;
}
 
export interface Transaction extends DatabaseConnection {
  rollback(): Promise<void>;
  commit(): Promise<void>;
}
 
export interface QueryBuilder<T> {
  where<K extends keyof T>(column: K, value: T[K]): this;
  where<K extends keyof T>(column: K, operator: string, value: any): this;
  where(condition: Partial<T>): this;
  select<K extends keyof T>(...columns: K[]): QueryBuilder<Pick<T, K>>;
  orderBy(column: keyof T, direction?: "asc" | "desc"): this;
  limit(count: number): this;
  offset(count: number): this;
  first(): Promise<T | null>;
  get(): Promise<T[]>;
  count(): Promise<number>;
  insert(data: Omit<T, "id">): Promise<T>;
  update(data: Partial<T>): Promise<number>;
  delete(): Promise<number>;
}

11.8 工具与资源

11.8.1 自动生成声明

# 从TypeScript源码生成声明
tsc --declaration --emitDeclarationOnly
 
# 使用dts-bundle打包声明
npx dts-bundle --name mylib --main dist/index.d.ts
 
# 从JSDoc生成
tsc --allowJs --declaration --emitDeclarationOnly

11.8.2 测试声明文件

// mylib-tests.ts
import { add, multiply, Calculator } from "mylib";
 
// $ExpectType number
const sum = add(1, 2);
 
// $ExpectError
add("1", "2");
 
// 测试类
const calc = new Calculator();
// $ExpectType number
calc.multiply(5);

11.9 小结

类型声明文件是TypeScript生态的关键组成部分:

1. 声明语法:declare关键字用于声明变量、函数、类、枚举等 2. 模块模式:支持全局库、模块库和UMD模式的声明 3. 高级类型:条件类型、映射类型、泛型约束等高级特性 4. 发布策略:与npm包一起发布或提交到DefinitelyTyped 5. 最佳实践:避免any、添加文档注释、保持版本兼容

掌握声明文件编写对于TypeScript开发者来说是一项重要技能,特别是在使用JavaScript库或为库添加TypeScript支持时。

typescript/第十一章类型声明文件.txt · 最后更改: 127.0.0.1