Skip to content
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待

模块 .d.ts

将 JavaScript 与示例 DTS 进行比较

常见的 CommonJS 模式

使用 CommonJS 模式的模块使用 module.exports 来描述导出的值。例如,这里是一个导出函数和数字常量的模块:

js
const maxInterval = 12;

function getArrayLength(arr) {
  return arr.length;
}

module.exports = {
  getArrayLength,
  maxInterval,
};

这可以通过以下 .d.ts 来描述:

ts
export function getArrayLength(arr: any[]): number;
export const maxInterval: 12;

TypeScript 演练场可以显示 JavaScript 代码对应的 .d.ts 等效内容。你可以在此处亲自尝试

.d.ts 语法有意看起来像 ES 模块 语法。 ES 模块于 2015 年被 TC39 批准为 ES2015 (ES6) 的一部分,虽然它早已通过转译器可用,但如果你有一个使用 ES 模块的 JavaScript 代码库:

js
export function getArrayLength(arr) {
  return arr.length;
}

这将具有以下 .d.ts 等效内容:

ts
export function getArrayLength(arr: any[]): number;

默认导出

在 CommonJS 中,你可以将任何值作为默认导出导出,例如这里是一个正则表达式模块:

js
module.exports = /hello( world)?/;

这可以通过以下 .d.ts 来描述:

ts
declare const helloWorld: RegExp;
export = helloWorld;

或者一个数字:

js
module.exports = 3.142;
ts
declare const pi: number;
export = pi;

CommonJS 中的一种导出风格是导出一个函数。 因为函数也是一个对象,所以可以添加额外字段并包含在导出中。

js
function getArrayLength(arr) {
  return arr.length;
}
getArrayLength.maxInterval = 12;

module.exports = getArrayLength;

这可以用以下方式描述:

ts
declare function getArrayLength(arr: any[]): number;
declare namespace getArrayLength {
  declare const maxInterval: 12;
}

export = getArrayLength;

有关其工作原理的详细信息,请参阅模块:函数模块参考页面。

处理多种消费导入

在现代消费代码中,有许多导入模块的方式:

ts
const fastify = require("fastify");
const { fastify } = require("fastify");
import fastify = require("fastify");
import * as Fastify from "fastify";
import { fastify, FastifyInstance } from "fastify";
import fastify from "fastify";
import fastify, { FastifyInstance } from "fastify";

要涵盖所有这些情况,需要 JavaScript 代码实际支持所有这些模式。 为了支持许多这样的模式,一个 CommonJS 模块需要看起来像这样:

js
class FastifyInstance {}

function fastify() {
  return new FastifyInstance();
}

fastify.FastifyInstance = FastifyInstance;

// 允许 { fastify }
fastify.fastify = fastify;
// 支持严格的 ES 模块
fastify.default = fastify;
// 设置默认导出
module.exports = fastify;

模块中的类型

你可能想要为不存在的 JavaScript 代码提供类型

js
function getArrayMetadata(arr) {
  return {
    length: getArrayLength(arr),
    firstObject: arr[0],
  };
}

module.exports = {
  getArrayMetadata,
};

这可以用以下方式描述:

ts
export type ArrayMetadata = {
  length: number;
  firstObject: any | undefined;
};
export function getArrayMetadata(arr: any[]): ArrayMetadata;

这个示例是使用泛型提供更丰富类型信息的好案例:

ts
export type ArrayMetadata<ArrType> = {
  length: number;
  firstObject: ArrType | undefined;
};

export function getArrayMetadata<ArrType>(
  arr: ArrType[]
): ArrayMetadata<ArrType>;

现在数组的类型会传播到 ArrayMetadata 类型中。

导出的类型随后可以被模块的消费者在 TypeScript 代码中使用 importimport type,或者在 JSDoc 导入中重用。

模块代码中的命名空间

试图描述 JavaScript 代码的运行时关系可能很棘手。 当类似 ES 模块的语法没有提供足够的工具来描述导出时,你可以使用 namespace

例如,你可能拥有足够复杂的类型,以至于你选择将它们命名空间化在 .d.ts 中:

ts
// 这表示将在运行时可用的 JavaScript 类
export class API {
  constructor(baseURL: string);
  getInfo(opts: API.InfoRequest): API.InfoResponse;
}

// 这个命名空间与 API 类合并,允许消费者和此文件
// 在其各自的部分中嵌套类型。
declare namespace API {
  export interface InfoRequest {
    id: string;
  }

  export interface InfoResponse {
    width: number;
    height: number;
  }
}

要了解命名空间在 .d.ts 文件中的工作方式,请阅读.d.ts 深入探究

可选的全局用法

你可以使用 export as namespace 声明你的模块将在 UMD 上下文中在全局作用域中可用:

ts
export as namespace moduleName;

参考示例

为了让您了解所有这些部分如何组合在一起,这里有一个参考 .d.ts,可以在创建新模块时作为起点

ts
// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]
// Project: [~THE PROJECT NAME~]
// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>

/*~ 这是模块模板文件。你应该将其重命名为 index.d.ts
 *~ 并将其放在与模块同名的文件夹中。
 *~ 例如,如果你正在为 "super-greeter" 编写文件,此
 *~ 文件应为 'super-greeter/index.d.ts'
 */

/*~ 如果此模块是一个 UMD 模块,在非模块加载器环境中加载时
 *~ 会暴露一个全局变量 'myLib',请在此处声明该全局变量。
 *~ 否则,删除此声明。
 */
export as namespace myLib;

/*~ 如果此模块导出函数,请像这样声明它们。
 */
export function myFunction(a: string): string;
export function myOtherFunction(a: number): number;

/*~ 你可以声明通过导入模块可用的类型 */
export interface SomeType {
  name: string;
  length: number;
  extras?: string[];
}

/*~ 你可以使用 const、let 或 var 声明模块的属性 */
export const myField: number;

库文件布局

你的声明文件的布局应镜像库的布局。

一个库可以由多个模块组成,例如

myLib
  +---- index.js
  +---- foo.js
  +---- bar
         +---- index.js
         +---- baz.js

这些可以导入为

js
var a = require("myLib");
var b = require("myLib/foo");
var c = require("myLib/bar");
var d = require("myLib/bar/baz");

因此,你的声明文件应该是

@types/myLib
  +---- index.d.ts
  +---- foo.d.ts
  +---- bar
         +---- index.d.ts
         +---- baz.d.ts

测试你的类型

如果你计划将这些更改提交到 DefinitelyTyped 供所有人使用,那么我们建议你:

  1. node_modules/@types/[libname] 中创建一个新文件夹
  2. 在该文件夹中创建 index.d.ts,并复制示例
  3. 查看你的模块使用中断的地方,并开始填充 index.d.ts
  4. 当你满意后,克隆 DefinitelyTyped/DefinitelyTyped 并按照 README 中的说明进行操作

否则

  1. 在源代码树的根目录中创建一个新文件:[libname].d.ts
  2. 添加 declare module "[libname]" { }
  3. 将模板添加到 declare module 的大括号内,并查看你的使用中断的地方