TypeScript 聲明文件結(jié)構(gòu)

2023-01-03 17:57 更新

概述

一般來講,你組織聲明文件的方式取決于庫是如何被使用的。 在JavaScript里提供了很多庫的使用方法,這就需要你書寫聲明文件去匹配它們。 這篇指南涵蓋了如何識別常見庫的模式,與怎么樣書寫符合相應(yīng)模式的聲明文件。

針對每種主要的庫的組織模式,在模版一節(jié)都有對應(yīng)的文件。 你可以利用它們幫助你快速上手。

識別庫的類型

首先,我們先看一下TypeScript聲明文件能夠表示的庫的類型。 這里會簡單展示每種類型的庫的使用方式,如何去書寫,還有一些真實(shí)案例。

識別庫的類型是書寫聲明文件的第一步。 我們將會給出一些提示,關(guān)于怎樣通過庫的 使用方法及其源碼來識別庫的類型。 根據(jù)庫的文檔及組織結(jié)構(gòu)不同,這兩種方式可能一個會比另外的那個簡單一些。 我們推薦你使用任意你喜歡的方式。

全局庫

全局庫是指能在全局命名空間下訪問的(例如:不需要使用任何形式的import)。 許多庫都是簡單的暴露出一個或多個全局變量。 比如,如果你使用過 jQuery,$變量可以被夠簡單的引用:

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

你經(jīng)常會在全局庫的指南文檔上看到如何在HTML里用腳本標(biāo)簽引用庫:

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

目前,大多數(shù)流行的全局訪問型庫實(shí)際上都以UMD庫的形式進(jìn)行書寫(見后文)。 UMD庫的文檔很難與全局庫文檔兩者之間難以區(qū)分。 在書寫全局聲明文件前,一定要確認(rèn)一下庫是否真的不是UMD。

從代碼上識別全局庫

全局庫的代碼通常都十分簡單。 一個全局的“Hello, world”庫可能是這樣的:

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

或這樣:

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

當(dāng)你查看全局庫的源代碼時,你通常會看到:

  • 頂級的var語句或function聲明
  • 一個或多個賦值語句到window.someName
  • 假設(shè)DOM原始值像documentwindow是存在的

不會看到:

  • 檢查是否使用或如何使用模塊加載器,比如requiredefine
  • CommonJS/Node.js風(fēng)格的導(dǎo)入如var fs = require("fs");
  • define(...)調(diào)用
  • 文檔里說明了如果require或?qū)脒@個庫

全局庫的例子

由于把一個全局庫轉(zhuǎn)變成UMD庫是非常容易的,所以很少流行的庫還再使用全局的風(fēng)格。 然而,小型的且需要DOM(或 沒有依賴)的庫可能還是全局類型的。

全局庫模版

模版文件global.d.ts定義了myLib庫作為例子。 一定要閱讀 "防止命名沖突"補(bǔ)充說明。

模塊化庫

一些庫只能工作在模塊加載器的環(huán)境下。 比如,像 express只能在Node.js里工作所以必須使用CommonJS的require函數(shù)加載。

ECMAScript 2015(也就是ES2015,ECMAScript 6或ES6),CommonJS和RequireJS具有相似的導(dǎo)入一個模塊的表示方法。 例如,對于JavaScript CommonJS (Node.js),有下面的代碼

var fs = require("fs");

對于TypeScript或ES6,import關(guān)鍵字也具有相同的作用:

import fs = require("fs");

你通常會在模塊化庫的文檔里看到如下說明:

var someLib = require('someLib');

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

});

與全局模塊一樣,你也可能會在UMD模塊的文檔里看到這些例子,因此要仔細(xì)查看源碼和文檔。

從代碼上識別模塊化庫

模塊庫至少會包含下列具有代表性的條目之一:

  • 無條件的調(diào)用requiredefine
  • import * as a from 'b'; or export c;這樣的聲明
  • 賦值給exportsmodule.exports

它們極少包含:

  • windowglobal的賦值

模塊化庫的例子

許多流行的Node.js庫都是這種模塊化的,例如express,gulp和 request。

UMD

UMD模塊是指那些既可以作為模塊使用(通過導(dǎo)入)又可以作為全局(在沒有模塊加載器的環(huán)境里)使用的模塊。 許多流行的庫,比如 Moment.js,就是這樣的形式。 比如,在Node.js或RequireJS里,你可以這樣寫:

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

然而在純凈的瀏覽器環(huán)境里你也可以這樣寫:

console.log(moment.format());

識別UMD庫

UMD模塊會檢查是否存在模塊加載器環(huán)境。 這是非常形容觀察到的模塊,它們會像下面這樣:

(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 define,typeof window,或typeof module這樣的測試,尤其是在文件的頂端,那么它幾乎就是一個UMD庫。

UMD庫的文檔里經(jīng)常會包含通過require“在Node.js里使用”例子, 和“在瀏覽器里使用”的例子,展示如何使用<script>標(biāo)簽去加載腳本。

UMD庫的例子

大多數(shù)流行的庫現(xiàn)在都能夠被當(dāng)成UMD包。 比如 jQuery,Moment.js,lodash和許多其它的。

模版

針對模塊有三種可用的模塊, module.d.tsmodule-class.d.ts and module-function.d.ts.

使用module-function.d.ts,如果模塊能夠作為函數(shù)調(diào)用。

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

一定要閱讀補(bǔ)充說明: “ES6模塊調(diào)用簽名的影響”

使用module-class.d.ts如果模塊能夠使用new構(gòu)造

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

相同的補(bǔ)充說明作用于這些模塊。

如果模塊不能被調(diào)用或構(gòu)造,使用module.d.ts文件。

模塊插件UMD插件

一個模塊插件可以改變一個模塊的結(jié)構(gòu)(UMD或模塊)。 例如,在Moment.js里, moment-range添加了新的range方法到monent對象。

對于聲明文件的目標(biāo),我們會寫相同的代碼不論被改變的模塊是一個純粹的模塊還是UMD模塊。

模版

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

全局插件

一個全局插件是全局代碼,它們會改變?nèi)謱ο蟮慕Y(jié)構(gòu)。 對于 全局修改的模塊,在運(yùn)行時存在沖突的可能。

比如,一些庫往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模版。

全局修改的模塊

當(dāng)一個全局修改的模塊被導(dǎo)入的時候,它們會改變?nèi)肿饔糜蚶锏闹怠?比如,存在一些庫它們添加新的成員到String.prototype當(dāng)導(dǎo)入它們的時候。 這種模式很危險,因?yàn)榭赡茉斐蛇\(yùn)行時的沖突, 但是我們?nèi)匀豢梢詾樗鼈儠鴮懧暶魑募?/p>

識別全局修改的模塊

全局修改的模塊通??梢院苋菀椎貜乃鼈兊奈臋n識別出來。 通常來講,它們與全局插件相似,但是需要 require調(diào)用來激活它們的效果。

你可能會看到像下面這樣的文檔:

// '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庫的依賴!

補(bǔ)充說明

防止命名沖突

注意,在書寫全局聲明文件時,允許在全局作用域里定義很多類型。 我們十分不建義這樣做,當(dāng)一個工程里有許多聲明文件時,它會導(dǎo)致無法處理的命名沖突。

一個簡單的規(guī)則是使用庫定義的全局變量名來聲明命名空間類型。 比如,庫定義了一個全局的值 cats,你可以這樣寫

declare namespace cats {
    interface KittySettings { }
}

不要

// at top-level
interface CatsKittySettings { }

這樣也保證了庫在轉(zhuǎn)換成UMD的時候沒有任何的破壞式改變,對于聲明文件用戶來說。

ES6模塊插件的影響

一些插件添加或修改已存在的頂層模塊的導(dǎo)出部分。 當(dāng)然這在CommonJS和其它加載器里是允許的,ES模塊被當(dāng)作是不可改變的因此這種模式就不可行了。 因?yàn)門ypeScript是能不預(yù)知加載器類型的,所以沒在編譯時保證,但是開發(fā)者如果要轉(zhuǎn)到ES6模塊加載器上應(yīng)該注意這一點(diǎn)。

ES6模塊調(diào)用簽名的影響

很多流行庫,比如Express,暴露出自己作為可以調(diào)用的函數(shù)。 比如,典型的Express使用方法如下:

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

在ES6模塊加載器里,頂層的對象(這里以exp導(dǎo)入)只能具有屬性; 頂層的模塊對象 永遠(yuǎn)不能被調(diào)用。 十分常見的解決方法是定義一個 default導(dǎo)出到一個可調(diào)用的/可構(gòu)造的對象; 一會模塊加載器助手工具能夠自己探測到這種情況并且使用 default導(dǎo)出來替換頂層對象。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號