全局: 插件

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.d.tsmodule-class.d.tsmodule-function.d.ts

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

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

请务必阅读脚注“ES6 对模块调用签名的影响”

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

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

这些模块也适用相同的脚注

如果您的模块不可调用或不可构造,请使用 module.d.ts 文件。

模块插件UMD 插件

模块插件 会改变另一个模块(UMD 或模块)的形状。例如,在 Moment.js 中,moment-range 会向 moment 对象添加一个新的 range 方法。

为了编写声明文件,无论被修改的模块是普通模块还是 UMD 模块,您都需要编写相同的代码。

模板

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

全局插件

全局插件 是更改某些全局变量形状的全局代码。与全局修改模块一样,这些插件会增加运行时冲突的可能性。

例如,一些库会向Array.prototypeString.prototype添加新函数。

识别全局插件

通常,从全局插件的文档中很容易识别它们。

你会看到类似这样的例子

js
var x = "hello, world";
// Creates new methods on built-in types
console.log(x.startsWithHello());
var y = [1, 2, 3];
// Creates new methods on built-in types
console.log(y.reverseAndSort());

模板

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

全局修改模块

全局修改模块在导入时会更改全局范围内的现有值。例如,可能存在一个库,它在导入时会向String.prototype添加新成员。这种模式有点危险,因为可能存在运行时冲突,但我们仍然可以为它编写声明文件。

识别全局修改模块

全局修改模块通常很容易从其文档中识别出来。一般来说,它们类似于全局插件,但需要一个 `require` 调用来激活它们的效果。

你可能会看到这样的文档

js
// 'require' call that doesn't use its return value
var unused = require("magic-string-time");
/* or */
require("magic-string-time");
var x = "hello, world";
// Creates new methods on built-in types
console.log(x.startsWithHello());
var y = [1, 2, 3];
// Creates new methods on built-in types
console.log(y.reverseAndSort());

模板

使用 global-modifying-module.d.ts 模板。

使用依赖项

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

全局库的依赖项

如果你的库依赖于全局库,请使用 `/// ` 指令

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 对模块插件的影响

一些插件在现有模块上添加或修改顶层导出。虽然这在 CommonJS 和其他加载器中是合法的,但 ES6 模块被认为是不可变的,这种模式将不再可能。由于 TypeScript 与加载器无关,因此没有对该策略进行编译时强制执行,但打算过渡到 ES6 模块加载器的开发人员应该注意这一点。

ES6 对模块调用签名的影响

许多流行的库,例如 Express,在导入时会将自身暴露为可调用函数。例如,典型的 Express 使用方式如下所示

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

在 ES6 模块加载器中,顶级对象(此处导入为 exp)只能具有属性;顶级模块对象永远不可调用。这里最常见的解决方案是为可调用/可构造对象定义一个 default 导出;一些模块加载器垫片会自动检测这种情况并将顶级对象替换为 default 导出。

库文件布局

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

库可以包含多个模块,例如

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
ts
// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]
// Project: [~THE PROJECT NAME~]
// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>
/*~ This template shows how to write a global plugin. */
/*~ Write a declaration for the original type and add new members.
*~ For example, this adds a 'toBinaryString' method with overloads to
*~ the built-in number type.
*/
interface Number {
toBinaryString(opts?: MyLibrary.BinaryFormatOptions): string;
toBinaryString(
callback: MyLibrary.BinaryFormatCallback,
opts?: MyLibrary.BinaryFormatOptions
): string;
}
/*~ If you need to declare several types, place them inside a namespace
*~ to avoid adding too many things to the global namespace.
*/
declare namespace MyLibrary {
type BinaryFormatCallback = (n: number) => string;
interface BinaryFormatOptions {
prefix?: string;
padding: number;
}
}

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

此页面的贡献者
MHMohamed Hegazy (53)
OTOrta Therox (16)
MFMartin Fischer (1)
JHJonathan Harrison (1)
JJohnny (1)
2+

上次更新时间:2024 年 3 月 21 日