库结构

总的来说,你组织声明文件的方式取决于库的使用方式。在 JavaScript 中,有很多方法可以提供库供使用,你需要编写与之匹配的声明文件。本指南介绍如何识别常见的库模式,以及如何编写与该模式相对应的声明文件。

每种主要的库结构模式在模板部分都有一个对应的文件。你可以从这些模板开始,帮助你更快地上手。

识别库类型

首先,我们将回顾 TypeScript 声明文件可以表示的库类型。我们将简要展示每种库的使用方式、编写方式,并列出一些来自现实世界的示例库。

识别库的结构是编写其声明文件的首要步骤。我们将根据库的使用代码提供识别结构的提示。根据库的文档和组织方式,其中一种方法可能比另一种更容易。我们建议使用您最舒适的方法。

您应该寻找什么?

在查看您尝试键入的库时,要问自己的问题。

  1. 您如何获取库?

    例如,您只能通过 npm 获取它,还是只能从 CDN 获取?

  2. 您将如何导入它?

    它是否添加了全局对象?它是否使用requireimport/export语句?

不同类型库的较小示例

模块化库

几乎所有现代的 Node.js 库都属于模块家族。这些类型的库只能在具有模块加载器的 JS 环境中使用。例如,express 只能在 Node.js 中使用,并且必须使用 CommonJS 的 require 函数加载。

ECMAScript 2015(也称为 ES2015、ECMAScript 6 和 ES6)、CommonJS 和 RequireJS 对导入模块的概念类似。例如,在 JavaScript CommonJS(Node.js)中,您将编写

js
var fs = require("fs");

在 TypeScript 或 ES6 中,import 关键字具有相同的用途

ts
import * as fs from "fs";

您通常会看到模块化库在其文档中包含以下行之一

js
var someLib = require("someLib");

js
define(..., ['someLib'], function(someLib) {
});

与全局模块一样,您可能会在UMD 模块的文档中看到这些示例,因此请务必检查代码或文档。

从代码中识别模块库

模块化库通常至少包含以下内容

  • requiredefine 的无条件调用
  • 声明,例如 import * as a from 'b';export c;
  • exportsmodule.exports 的赋值

它们很少有

  • windowglobal 属性的赋值

模块模板

模块有四种模板可用,module.d.tsmodule-class.d.tsmodule-function.d.tsmodule-plugin.d.ts

您应该首先阅读 module.d.ts,以了解它们的工作原理。

如果您的模块可以像函数一样调用,请使用模板 module-function.d.ts

js
const x = require("foo");
// Note: calling 'x' as a function
const y = x(42);

如果您的模块可以使用 new构造,请使用模板 module-class.d.ts

js
const x = require("bar");
// Note: using 'new' operator on the imported variable
const y = new x("hello");

如果您有一个模块,在导入时会对其他模块进行更改,请使用模板 module-plugin.d.ts

js
const jest = require("jest");
require("jest-matchers-files");

全局库

全局库是可以从全局范围访问的库(即,无需使用任何形式的 import)。许多库只是公开一个或多个全局变量以供使用。例如,如果您使用的是 jQuery,则可以通过简单地引用 $ 变量来使用它。

ts
$(() => {
console.log("hello!");
});

通常,您会在全局库的文档中看到有关如何在 HTML 脚本标签中使用该库的指南。

html
<script src="http://a.great.cdn.for/someLib.js"></script>

如今,大多数流行的全局可访问库实际上都是用 UMD 库编写的(见下文)。UMD 库文档很难与全局库文档区分开来。在编写全局声明文件之前,请确保该库实际上不是 UMD 库。

从代码中识别全局库

全局库代码通常非常简单。一个全局的“Hello, world”库可能看起来像这样

js
function createGreeting(s) {
return "Hello, " + s;
}

或者像这样

js
// Web
window.createGreeting = function (s) {
return "Hello, " + s;
};
// Node
global.createGreeting = function (s) {
return "Hello, " + s;
};
// Potentially any runtime
globalThis.createGreeting = function (s) {
return "Hello, " + s;
};

在查看全局库的代码时,通常会看到

  • 顶层的var语句或function声明
  • 一个或多个对window.someName的赋值
  • 假设DOM原语(如documentwindow)存在

不会看到

  • 对模块加载器(如requiredefine)的检查或使用
  • CommonJS/Node.js风格的导入,形式为var fs = require("fs");
  • define(...)的调用
  • 描述如何require或导入库的文档

全局库示例

由于通常很容易将全局库转换为UMD库,因此很少有流行的库仍然以全局样式编写。但是,体积小且需要DOM(或没有依赖项)的库可能仍然是全局的。

全局库模板

模板文件global.d.ts定义了一个示例库myLib。请务必阅读“防止命名冲突”脚注

UMD

一个UMD 模块可以作为模块(通过导入)使用,作为全局变量(在没有模块加载器的环境中运行)使用。许多流行的库,例如 Moment.js,都是这样编写的。例如,在 Node.js 或使用 RequireJS 中,你会这样写

ts
import moment = require("moment");
console.log(moment.format());

而在普通的浏览器环境中,你会这样写

js
console.log(moment.format());

识别 UMD 库

UMD 模块 会检查模块加载器环境是否存在。这是一个很容易识别的模式,看起来像这样

js
(function (root, factory) {
if (typeof define === "function" && define.amd) {
define(["libName"], factory);
} else if (typeof module === "object" && module.exports) {
module.exports = factory(require("libName"));
} else {
root.returnExports = factory(root.libName);
}
}(this, function (b) {

如果你在库代码中看到对 typeof definetypeof windowtypeof module 的测试,尤其是在文件开头,它几乎总是 UMD 库。

UMD 库的文档通常也会展示一个“在 Node.js 中使用”的示例,显示 require,以及一个“在浏览器中使用”的示例,显示使用 <script> 标签加载脚本。

UMD 库示例

现在大多数流行的库都以 UMD 包的形式提供。例如 jQueryMoment.jslodash 等等。

模板

使用 module-plugin.d.ts 模板。

使用依赖项

您的库可能有多种依赖项。本节介绍如何将它们导入声明文件。

对全局库的依赖项

如果您的库依赖于全局库,请使用 /// <reference types="..." /> 指令

ts
/// <reference types="someLib" />
function getThing(): someLib.thing;

对模块的依赖项

如果您的库依赖于模块,请使用 import 语句

ts
import * as moment from "moment";
function getThing(): moment;

对 UMD 库的依赖项

从全局库中

如果您的全局库依赖于 UMD 模块,请使用 /// <reference types 指令

ts
/// <reference types="moment" />
function getThing(): moment;

从模块或 UMD 库中

如果您的模块或 UMD 库依赖于 UMD 库,请使用 import 语句

ts
import * as someLib from "someLib";

不要使用 /// <reference 指令来声明对 UMD 库的依赖关系!

脚注

防止命名冲突

请注意,在编写全局声明文件时,可以在全局范围内定义多种类型。我们强烈建议不要这样做,因为当项目中存在多个声明文件时,这会导致可能无法解决的名称冲突。

一个简单的规则是,只声明由库定义的任何全局变量命名空间的类型。例如,如果库定义了全局值“cats”,则应编写

ts
declare namespace cats {
interface KittySettings {}
}

不要

ts
// at top-level
interface CatsKittySettings {}

此指南还确保库可以转换为 UMD,而不会破坏声明文件用户。

ES6 对模块调用签名的影响

许多流行的库,例如 Express,在导入时将自己公开为可调用函数。例如,典型的 Express 用法如下所示

ts
import exp = require("express");
var app = exp();

在符合 ES6 的模块加载器中,顶层对象(此处导入为 exp)只能具有属性;顶层模块对象永远不可调用。

这里最常见的解决方案是为可调用/可构造对象定义一个default导出;模块加载器通常会自动检测这种情况,并将顶层对象替换为default导出。如果您在 tsconfig.json 中有"esModuleInterop": true,TypeScript 可以为您处理此问题。

TypeScript 文档是一个开源项目。帮助我们改进这些页面 通过发送 Pull Request

此页面的贡献者
MHMohamed Hegazy (57)
OTOrta Therox (17)
DRDaniel Rose (3)
Llilichao (1)
MFMartin Fischer (1)
12+

上次更新:2024 年 3 月 21 日