模块 .d.ts

将 JavaScript 与示例 DTS 进行比较

¥Comparing JavaScript to an example DTS

常见的 CommonJS 模式

¥Common CommonJS Patterns

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

¥A module using CommonJS patterns uses module.exports to describe the exported values. For example, here is a module which exports a function and a numerical constant:

js
const maxInterval = 12;
function getArrayLength(arr) {
return arr.length;
}
module.exports = {
getArrayLength,
maxInterval,
};

这可以用以下 .d.ts 来描述:

¥This can be described by the following .d.ts:

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

TypeScript playground 可以向你展示 JavaScript 代码的 .d.ts 等价物。你可以 在这里自己试试

¥The TypeScript playground can show you the .d.ts equivalent for JavaScript code. You can try it yourself here.

.d.ts 语法故意看起来像 ES 模块 语法。ES 模块在 2015 年作为 ES2015 (ES6) 的一部分被 TC39 批准,虽然它已经通过转译器提供了很长时间,但是如果你有一个使用 ES 模块的 JavaScript 代码库:

¥The .d.ts syntax intentionally looks like ES Modules syntax. ES Modules was ratified by TC39 in 2015 as part of ES2015 (ES6), while it has been available via transpilers for a long time, however if you have a JavaScript codebase using ES Modules:

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

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

¥This would have the following .d.ts equivalent:

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

默认导出

¥Default Exports

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

¥In CommonJS you can export any value as the default export, for example here is a regular expression module:

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

可以用以下 .d.ts 来描述:

¥Which can be described by the following .d.ts:

ts
declare const helloWorld: RegExp;
export default helloWorld;

或者一个数字:

¥Or a number:

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

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

¥One style of exporting in CommonJS is to export a function. Because a function is also an object, then extra fields can be added and are included in the export.

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

可以描述为:

¥Which can be described with:

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

请注意,在 .d.ts 文件中使用 export default 需要 esModuleInterop: true 才能工作。如果你的项目中没有 esModuleInterop: true,例如当你向 Definitely Typed 提交 PR 时,你将不得不使用 export= 语法。这种较旧的语法更难使用,但可以在任何地方使用。以下是必须使用 export= 编写上述示例的方式:

¥Note that using export default in your .d.ts files requires esModuleInterop: true to work. If you can’t have esModuleInterop: true in your project, such as when you’re submitting a PR to Definitely Typed, you’ll have to use the export= syntax instead. This older syntax is harder to use but works everywhere. Here’s how the above example would have to be written using export=:

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

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

¥See Module: Functions for details of how that works, and the Modules reference page.

处理大量引用导入

¥Handling Many Consuming Import

在现代引用代码中有多种方式导入模块:

¥There are many ways to import a module in modern consuming code:

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 模块需要看起来像这样:

¥Covering all of these cases requires the JavaScript code to actually support all of these patterns. To support many of these patterns, a CommonJS module would need to look something like:

js
class FastifyInstance {}
function fastify() {
return new FastifyInstance();
}
fastify.FastifyInstance = FastifyInstance;
// Allows for { fastify }
fastify.fastify = fastify;
// Allows for strict ES Module support
fastify.default = fastify;
// Sets the default export
module.exports = fastify;

模块中的类型

¥Types in Modules

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

¥You may want to provide a type for JavaScript code which does not exist

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

这可以描述为:

¥This can be described with:

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

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

¥This example is a good case for using generics to provide richer type information:

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

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

¥Now the type of the array propagates into the ArrayMetadata type.

然后,导出的类型可以由模块的使用者使用 TypeScript 代码中的 importimport typeJSDoc 导入 重新使用。

¥The types which are exported can then be re-used by consumers of the modules using either import or import type in TypeScript code or JSDoc imports.

模块代码中的命名空间

¥Namespaces in Module Code

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

¥Trying to describe the runtime relationship of JavaScript code can be tricky. When the ES Module-like syntax doesn’t provide enough tools to describe the exports then you can use namespaces.

例如,你可能有足够复杂的类型来描述你选择在 .d.ts 中为它们命名空间:

¥For example, you may have complex enough types to describe that you choose to namespace them inside your .d.ts:

ts
// This represents the JavaScript class which would be available at runtime
export class API {
constructor(baseURL: string);
getInfo(opts: API.InfoRequest): API.InfoResponse;
}
// This namespace is merged with the API class and allows for consumers, and this file
// to have types which are nested away in their own sections.
declare namespace API {
export interface InfoRequest {
id: string;
}
export interface InfoResponse {
width: number;
height: number;
}
}

要了解名称空间在 .d.ts 文件中的工作原理,请阅读 .d.ts 深入探讨

¥To understand how namespaces work in .d.ts files read the .d.ts deep dive.

可选的全局用法

¥Optional Global Usage

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

¥You can use export as namespace to declare that your module will be available in the global scope in UMD contexts:

ts
export as namespace moduleName;

参考范例

¥Reference Example

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

¥To give you an idea of how all these pieces can come together, here is a reference .d.ts to start with when making a new module

ts
// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]
// Project: [~THE PROJECT NAME~]
// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>
/*~ This is the module template file. You should rename it to index.d.ts
*~ and place it in a folder with the same name as the module.
*~ For example, if you were writing a file for "super-greeter", this
*~ file should be 'super-greeter/index.d.ts'
*/
/*~ If this module is a UMD module that exposes a global variable 'myLib' when
*~ loaded outside a module loader environment, declare that global here.
*~ Otherwise, delete this declaration.
*/
export as namespace myLib;
/*~ If this module exports functions, declare them like so.
*/
export function myFunction(a: string): string;
export function myOtherFunction(a: number): number;
/*~ You can declare types that are available via importing the module */
export interface SomeType {
name: string;
length: number;
extras?: string[];
}
/*~ You can declare properties of the module using const, let, or var */
export const myField: number;

库文件布局

¥Library file layout

声明文件的布局应该反映库的布局。

¥The layout of your declaration files should mirror the layout of the library.

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

¥A library can consist of multiple modules, such as

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

这些可以导入为

¥These could be imported as

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

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

¥Your declaration files should thus be

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

测试你的类型

¥Testing your types

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

¥If you are planning on submitting these changes to DefinitelyTyped for everyone to also use, then we recommend you:

  1. node_modules/@types/[libname] 中创建一个新文件夹

    ¥Create a new folder in node_modules/@types/[libname]

  2. 在该文件夹中创建一个 index.d.ts,并将示例复制到

    ¥Create an index.d.ts in that folder, and copy the example in

  3. 查看你对模块的使用在哪里中断,并开始填写 index.d.ts

    ¥See where your usage of the module breaks, and start to fill out the index.d.ts

  4. 如果你满意,请克隆 DefinitelyTyped/DefinitelyTyped 并按照 README 中的说明进行操作。

    ¥When you’re happy, clone DefinitelyTyped/DefinitelyTyped and follow the instructions in the README.

否则

¥Otherwise

  1. 在源代码树的根目录中创建一个新文件:[libname].d.ts

    ¥Create a new file in the root of your source tree: [libname].d.ts

  2. 添加 declare module "[libname]" { }

    ¥Add declare module "[libname]" { }

  3. 在 declare 模块的大括号内添加模板,看看你的用法在哪里中断

    ¥Add the template inside the braces of the declare module, and see where your usage breaks