<返回目录     Powered by claud/xia兄

第12课: 声明文件

什么是声明文件?

声明文件(.d.ts)用于为JavaScript库提供类型信息。它只包含类型声明,不包含实现代码。

基本声明

// myLib.d.ts
declare const myLib: {
  version: string;
  init(config: object): void;
};

declare function greet(name: string): string;

declare class Calculator {
  add(a: number, b: number): number;
  subtract(a: number, b: number): number;
}

模块声明

// lodash.d.ts
declare module "lodash" {
  export function chunk(array: T[], size: number): T[][];
  export function compact(array: T[]): T[];
  export function concat(...arrays: T[][]): T[];
}

// 使用
import { chunk } from "lodash";
const result = chunk([1, 2, 3, 4], 2);

全局声明

// global.d.ts
declare global {
  interface Window {
    myCustomProperty: string;
  }

  var MY_GLOBAL_VAR: string;

  function myGlobalFunction(): void;
}

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

命名空间声明

// jquery.d.ts
declare namespace jQuery {
  function ajax(url: string, settings?: any): void;

  namespace fn {
    function extend(object: any): void;
  }
}

declare var $: typeof jQuery;

UMD模块声明

// math.d.ts
export as namespace math;

export function add(a: number, b: number): number;
export function subtract(a: number, b: number): number;

// 可以作为全局变量使用
// 
// math.add(1, 2);

// 也可以作为模块导入
// import { add } from "math";

@types包

DefinitelyTyped是一个社区维护的类型声明仓库:

# 安装类型声明
npm install --save-dev @types/node
npm install --save-dev @types/express
npm install --save-dev @types/react

# tsconfig.json
{
  "compilerOptions": {
    "types": ["node", "express"]
  }
}

声明合并

// 接口合并
interface Box {
  height: number;
  width: number;
}

interface Box {
  scale: number;
}

let box: Box = { height: 5, width: 6, scale: 10 };

// 命名空间合并
namespace Animals {
  export class Zebra {}
}

namespace Animals {
  export interface Legged {
    numberOfLegs: number;
  }
  export class Dog {}
}

模块扩展

// observable.d.ts
export class Observable {
  // ...
}

// map.d.ts
import { Observable } from "./observable";

declare module "./observable" {
  interface Observable {
    map(f: (x: T) => U): Observable;
  }
}

// 现在Observable有map方法了

三斜线指令

/// 
/// 
/// 

// 使用声明的类型
const value: MyCustomType = {};

为JavaScript库编写声明文件

// myLib.js
function createGreeting(name) {
  return "Hello, " + name;
}

module.exports = {
  createGreeting: createGreeting,
  version: "1.0.0"
};

// myLib.d.ts
export function createGreeting(name: string): string;
export const version: string;

复杂类型声明

// api.d.ts
declare namespace API {
  interface RequestConfig {
    url: string;
    method?: "GET" | "POST" | "PUT" | "DELETE";
    headers?: Record;
    data?: any;
  }

  interface Response {
    data: T;
    status: number;
    statusText: string;
  }

  function request(config: RequestConfig): Promise>;
}

// 使用
API.request({
  url: "/api/users/1",
  method: "GET"
}).then(response => {
  console.log(response.data.name);
});

条件类型声明

// utils.d.ts
type IsArray = T extends any[] ? true : false;

type Flatten = T extends Array ? U : T;

type ReturnType = T extends (...args: any[]) => infer R ? R : any;

发布声明文件

// package.json
{
  "name": "my-library",
  "version": "1.0.0",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",  // 指向声明文件
  "files": [
    "dist"
  ]
}

// tsconfig.json
{
  "compilerOptions": {
    "declaration": true,        // 生成声明文件
    "declarationMap": true,     // 生成声明文件的source map
    "outDir": "./dist"
  }
}
声明文件最佳实践:
  • 优先使用@types包,避免重复造轮子
  • 使用declare关键字声明全局变量和函数
  • 为第三方库编写声明文件时,考虑贡献到DefinitelyTyped
  • 使用/// 指令管理依赖关系
  • 发布库时包含声明文件
练习:
  1. 为一个简单的JavaScript库编写声明文件
  2. 扩展Window接口,添加自定义属性
  3. 使用声明合并扩展第三方库的类型
  4. 配置项目自动生成声明文件