在线文档教程

Library Structures

库结构

概观

一般来说,您构建声明文件的方式取决于库的使用方式。在JavaScript中提供用于消费的库的方法很多,您需要编写您的声明文件以匹配它。本指南介绍了如何识别公共库模式,以及如何编写与该模式相对应的声明文件。

每种主要的图书馆结构模式都在模板部分有相应的文件。您可以从这些模板开始,帮助您更快地完成工作。

识别各种图书馆

首先,我们将回顾TypeScript声明文件可以表示的类库。我们将说明如何每种库时使用,它是如何写的,而在现实世界中列举了一些例子库。

识别库的结构是编写其声明文件的第一步。我们将提示如何根据用法代码来识别结构。根据图书馆的文件和组织,一个可能比另一个更容易。我们建议您选择使用更舒适的产品。

全局图书馆

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

$(() => { console.log('hello!' }

您通常会在全局库的文档中看到如何在HTML脚本标记中使用库的指导:

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

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

从代码中识别全局库

全球图书馆代码通常非常简单。一个全球性的“你好,世界”图书馆可能是这样的:

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

或者像这样:

window.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。请务必阅读“防止名称冲突”脚注。

模块化图书馆

一些库只能在模块加载器环境中工作。例如,因为express只能在Node.js中工作,并且必须使用CommonJS require函数加载。

ECMAScript的2015(也称为ES2015,ECMAScript的6和ES6),CommonJS的,和RequireJS具有类似概念导入一个模块。例如,在JavaScript CommonJS(Node.js)中,你会写

var fs = require("fs"

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

import fs = require("fs"

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

var someLib = require('someLib'

要么

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

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

从代码中识别模块库

模块化库通常至少具有以下一些内容:

  • 无条件调用requiredefine

  • 声明像import * as a from 'b';export c;

  • 分配给exportsmodule.exports

他们很少会有:

  • 分配属性windowglobal

模块化库的例子

许多流行的Node.js库的模块系列,如expressgulprequest

UMD

UMD模块是一个可以或者被用作模块(通过一个进口),或作为一个全局(当在环境中时不带模块加载程序运行)。许多流行的库,如Moment.js,都是这样写的。例如,在Node.js或使用RequireJS,你会写:

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

而在香草浏览器环境中,您可以编写:

console.log(moment.format()

识别UMD库

UMD模块检查是否存在模块加载器环境。这是一个易于理解的模式,看起来像这样:

(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库的文档通常还会演示一个“Using in Node.js”示例require,以及一个“在浏览器中使用”示例,显示使用<script>标签加载脚本。

UMD库的例子

现在大多数流行的图书馆都可以作为UMD套餐。例子包括jQueryMoment.jslodash等等。

模板

有三个模板可用于模块module.d.tsmodule-class.d.tsmodule-function.d.ts

使用module-function.d.ts,如果你的模块可以被称为像函数:

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

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

使用module-class.d.ts,如果你的模块可以构建使用new

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添加了一个新range方法moment

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

模板

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

全局插件

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

例如,一些库添加新的功能Array.prototypeString.prototype

识别全球插件

全球插件通常很容易从他们的文档中识别。

你会看到如下所示的例子:

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调用来激活它们的效果。

你可能会看到像这样的文档:

// '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模板。

消费依赖

您可能有几种依赖关系。

全局库的依赖关系

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

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

模块上的依赖关系

如果您的库依赖于某个模块,请使用以下import语句:

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

UMD库的依赖

来自全局库

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

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

来自模块或UMD库

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

import * as someLib from 'someLib';

千万不能使用/// <reference指令声明依赖于UMD库!

脚注

防止名称冲突

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

遵循的一条简单规则是只声明由库定义的任何全局变量的名称空间类型。例如,如果库定义了全局值'猫',你应该写

declare namespace cats { interface KittySettings { } }

不是

// at top-level interface CatsKittySettings { }

该指导还确保了该库可以过渡到UMD而不会破坏声明文件用户。

ES6对模块插件的影响

一些插件添加或修改现有模块的顶级导出。虽然在CommonJS和其他装载器中这是合法的,但ES6模块被认为是不可变的,这种模式将不可能实现。因为TypeScript是与loader无关的,所以没有编译时执行这个策略,但是打算转换到ES6模块加载器的开发人员应该知道这一点。

ES6对模块呼叫签名的影响

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

import exp = require("express" var app = exp(

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