TS 3.1 - 模塊(導入導出)


總結

  • 同步導出
    - export { ZipCodeValidator as mainValidator }重命名導出
    - export * from "./StringValidator";重新導出export { test } from "./Calculator";
    - export default function (s: string) {...}普通默認導出,標記為默認導出的類和函數的名字是可以省略的。
  • 同步導入
    - import { ZipCodeValidator as ZCV } from "./ZipCodeValidator";重命名導入
    - import * as validator from "./ZipCodeValidator";整個模塊導入
    - import "./my-module.js";直接導入,獲取導出對象,直接創建關聯關系,通常用於導入內部進行了某系操作的模塊
    - import validate, { other } from "./StaticZipCodeValidator";普通導入
  • 異步導入
    - 可能是由於該文檔比較舊,import(...)是 ES6 用於支持異步導入的。在 TS 4.1.2 中支持這個寫法
  • 默認導入導出
    - 非 ES6 的默認導入導出, export default 語法並不能兼容 CommonJS 和 AMD 的exports
    - export = ZipCodeValidator;非 ES6 的默認導出,若使用export =導出一個模塊,則必須使用 TypeScript 的特定語法import module = require("module")來導入此模塊。
  • 特點
    - 含有exportimport的都是外部模塊,也就是 ES6 中的模塊。
    - 模塊中導出的引用類數據,一旦在模塊外被修改,模塊內的也會被修改。即,導入只是導入了該對象的引用。
    - 只引入模塊中的類型,TS 打包時不會打包這些模塊。應該是有配置項可以配置引入類型時要寫入typeof關鍵字
    - tsconfig 中的 include 聲明的是 TS 在進行類型檢查時需要包含的文件,即使這些文件沒有真實的引用關系。
    - 在 TS 中可以導入 .d.ts 文件,但是編譯后如果沒有對應的 JS 文件便會報模塊丟失。可以在項目中的 .d.ts 文件導入這些外部聲明,或者在 tsconfig 中配置 include 包含這些文件。
  • declare module用於外部模塊類型的聲明
    - 一個包含導入導出的 .d.ts 文件可以被認為是一個模塊的類型
    - 即使在declare module已經寫了實現,但依然只是類型的聲明;
    - 支持外部模塊嵌入式擴展的聲明擴展,例如class.prototype的嵌入式擴展,可以使用declare module擴展聲明。
    - 普通的模塊擴展使用import引入后再導出,實現實體和類型的同時擴展。
    - 模塊名為字符串,遵循模塊路徑的解析規則。會自動注冊為路徑對應模塊的類型,相同名稱的模塊支持聲明合並。即可以擴展一個 .d.ts 文件。
    - 可以包含exportexport default,表示該模塊的導出的屬性的類型和默認導出的類型。
    - declare module "hot-new-module";僅僅聲明模塊本身,模塊內允許包含任意屬性,這些屬性的類型都是 any;
    - declare module "*!text" {...}聲明一個可匹配一類模塊的類型,它能夠為import fileContent from "./xyz.txt!text";提供類型
  • 在 .d.ts 文件中export as namespace mathLib;把該文件內的所有 export 作為一個命名空間,聲明在全局有個 mathLib 對象。同時聲明了該 .d.ts 對應的模塊擁有的導出對象的類型。
  • TS 中類型是通過搜集得到的,由 tsconfig 中的 include 標明,會搜集不具有導入導出的 .ts.d.ts。通過明文導入也會擴展類型搜集范圍
    - 在模塊模式中當要聲明全局變量時使用declare global,或者說它叫所有模塊注入變量會更准確點。
    - declare var d3:string在無導入導出文件中,聲明一個全局變量 d3 的存在,並在他的類型是 string
    - declare global下的interface Window用來擴展 window 對象
  • 一個 ts 模塊編譯后應編譯成兩部分,一部分是由 JS 文件提供實現,一部分是由 .d.ts 提供的類型支持,供外部使用。

正文

原文地址 www.tslang.cn

關於術語的一點說明: 請務必注意一點,TypeScript 1.5 里術語名已經發生了變化。 “內部模塊”現在稱做 “命名空間”。 “外部模塊” 現在則簡稱為“模塊”,這是為了與 ECMAScript 2015 里的術語保持一致,(也就是說 module X { 相當於現在推薦的寫法 namespace X {)。

介紹

從 ECMAScript 2015 開始,JavaScript 引入了模塊的概念。TypeScript 也沿用這個概念。

模塊使用模塊加載器去導入其它的模塊。 在運行時,模塊加載器的作用是在執行此模塊代碼前去查找並執行這個模塊的所有依賴。 大家最熟知的 JavaScript 模塊加載器是服務於 Node.js 的 CommonJS 和服務於 Web 應用的 Require.js

注釋:AMD是模塊規范,Require.js是這個規范的實現。
注釋:這種全局可見需要配合<script>標簽引入,不然只是類型上的全局可見,無法訪問實現。

TypeScript 與 ECMAScript 2015 一樣,任何包含頂級import或者export的文件都被當成一個模塊。相反地,如果一個文件不帶有頂級的import或者export聲明,那么它的內容被視為全局可見的(因此對模塊也是可見的)。

導出

導出聲明

注釋:在 TS 中,類型和實現都可以作為模塊的導入和導出使用

任何聲明(比如變量,函數,類,類型別名或接口)都能夠通過添加export關鍵字來導出。

Validation.ts
export interface StringValidator {
    isAcceptable(s: string): boolean;
}
ZipCodeValidator.ts
export const numberRegexp = /^[0-9]+$/;

export class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) {
        return s.length === 5 && numberRegexp.test(s);
    }
}

導出語句

導出語句很便利,因為我們可能需要對導出的部分重命名,所以上面的例子可以這樣改寫:

class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) {
        return s.length === 5 && numberRegexp.test(s);
    }
}
export { ZipCodeValidator };
export { ZipCodeValidator as mainValidator };

重新導出

我們經常會去擴展其它模塊,並且只導出那個模塊的部分內容。 重新導出功能並不會在當前模塊導入那個模塊或定義一個新的局部變量。

ParseIntBasedZipCodeValidator.ts
export class ParseIntBasedZipCodeValidator {
    isAcceptable(s: string) {
        return s.length === 5 && parseInt(s).toString() === s;
    }
}

// 導出原先的驗證器但做了重命名
export {ZipCodeValidator as RegExpBasedZipCodeValidator} from "./ZipCodeValidator";

或者一個模塊可以包裹多個模塊,並把他們導出的內容聯合在一起通過語法:export * from "module"

AllValidators.ts
export * from "./StringValidator"; // exports interface StringValidator
export * from "./LettersOnlyValidator"; // exports class LettersOnlyValidator
export * from "./ZipCodeValidator";  // exports class ZipCodeValidator

導入

導入一個模塊中的某個導出內容

可以對導入內容重命名

import { ZipCodeValidator as ZCV } from "./ZipCodeValidator";
let myValidator = new ZCV();

將整個模塊導入到一個變量,並通過它來訪問模塊的導出部分

import * as validator from "./ZipCodeValidator";
let myValidator = new validator.ZipCodeValidator();

具有副作用的導入模塊

盡管不推薦這么做,一些模塊會設置一些全局狀態供其它模塊使用。 這些模塊可能沒有任何的導出或用戶根本就不關注它的導出。 使用下面的方法來導入這類模塊:

注釋:沒有任何導出的模塊即使通過import也不會把模塊內的所有變量全局注冊。和這句有沖突?

import "./my-module.js";

默認導出

類和函數聲明可以直接被標記為默認導出。 標記為默認導出的類和函數的名字是可以省略的。

StaticZipCodeValidator.ts
const numberRegexp = /^[0-9]+$/;

export default function (s: string) {
    return s.length === 5 && numberRegexp.test(s);
}
Test.ts
import validate from "./StaticZipCodeValidator";

let strings = ["Hello", "98052", "101"];

// Use function validate
strings.forEach(s => {
  console.log(`"${s}" ${validate(s) ? " matches" : " does not match"}`);
});

default導出也可以是一個值

OneTwoThree.ts
export default "123";
Log.ts
import num from "./OneTwoThree";

console.log(num); // "123"

export = 和 import = require()

CommonJS 和 AMD 的環境里都有一個exports變量,這個變量包含了一個模塊的所有導出內容。

CommonJS 和 AMD 的exports都可以被賦值為一個對象, 這種情況下其作用就類似於 es6 語法里的默認導出,即 export default語法了。雖然作用相似,但是 export default 語法並不能兼容 CommonJS 和 AMD 的exports

為了支持 CommonJS 和 AMD 的exports, TypeScript 提供了export =語法。

export =語法定義一個模塊的導出對象。 這里的對象一詞指的是類,接口,命名空間,函數或枚舉。

若使用export =導出一個模塊,則必須使用 TypeScript 的特定語法import module = require("module")來導入此模塊。

ZipCodeValidator.ts
let numberRegexp = /^[0-9]+$/;
class ZipCodeValidator {
    isAcceptable(s: string) {
        return s.length === 5 && numberRegexp.test(s);
    }
}
export = ZipCodeValidator;
Test.ts
import zip = require("./ZipCodeValidator");

// Some samples to try
let strings = ["Hello", "98052", "101"];

// Validators to use
let validator = new zip();

// Show whether each string passed each validator
strings.forEach(s => {
  console.log(`"${ s }" - ${ validator.isAcceptable(s) ? "matches" : "does not match" }`);
});

簡單示例

注釋:以下是一個編譯 TS 文件為目標文件的示例

下面我們來整理一下前面的驗證器實現,每個模塊只有一個命名的導出。

為了編譯,我們必需要在命令行上指定一個模塊目標。對於 Node.js 來說,使用--module commonjs; 對於 Require.js 來說,使用--module amd。比如:

tsc --module commonjs Test.ts

編譯完成后,每個模塊會生成一個單獨的.js文件。 好比使用了 reference 標簽,編譯器會根據 import語句編譯相應的文件。

可選的模塊加載和其它高級加載場景

注釋:在書寫時只引入了該模塊的類型,不會打包這個引入的模塊。TS 可以根據需要在引用到時才按需引入,CommonJS 雖然是同步的但也有按需的同步引入

有時候,你只想在某種條件下才加載某個模塊。 在 TypeScript 里,使用下面的方式來實現它和其它的高級加載場景,我們可以直接調用模塊加載器並且可以保證類型完全。

編譯器會檢測是否每個模塊都會在生成的 JavaScript 中用到。 如果一個模塊標識符只在類型注解部分使用,並且完全沒有在表達式中使用時,就不會生成 require這個模塊的代碼。 省略掉沒有用到的引用對性能提升是很有益的,並同時提供了選擇性加載模塊的能力。

注釋:import id = require("...")能夠實現按需引入,下文應該是有誤的,這個方法不是為了訪問模塊導出的類型。
注釋:由於 import 是同步引入,為了把這部分打包成按需引入所以只允許使用 import 引入的類型,為了確保安全應該加添typeof關鍵字(有校驗配置項可以設置為:如果引入只是使用類型的話 import 上必須加上 typeof)
注釋:在 4.1.2 版本中支持 import("...")這種 ES6 的異步加載語法

這種模式的核心是import id = require("...")語句可以讓我們訪問模塊導出的類型。 模塊加載器會被動態調用(通過 require),就像下面if代碼塊里那樣。 它利用了省略引用的優化,所以模塊只在被需要時加載。 為了讓這個模塊工作,一定要注意 import定義的標識符只能在表示類型處使用(不能在會轉換成 JavaScript 的地方)。

為了確保類型安全性,我們可以使用typeof關鍵字。 typeof關鍵字,當在表示類型的地方使用時,會得出一個類型值,這里就表示模塊的類型。

注釋:下面應該是三種加載器打包后的結果

示例:Node.js 里的動態模塊加載
declare function require(moduleName: string): any;

import { ZipCodeValidator as Zip } from "./ZipCodeValidator";

if (needZipValidation) {
    let ZipCodeValidator: typeof Zip = require("./ZipCodeValidator");
    let validator = new ZipCodeValidator();
    if (validator.isAcceptable("...")) { /* ... */ }
}
示例:require.js 里的動態模塊加載
declare function require(moduleNames: string[], onLoad: (...args: any[]) => void): void;

import * as Zip from "./ZipCodeValidator";

if (needZipValidation) {
    require(["./ZipCodeValidator"], (ZipCodeValidator: typeof Zip) => {
        let validator = new ZipCodeValidator.ZipCodeValidator();
        if (validator.isAcceptable("...")) { /* ... */ }
    });
}
示例:System.js 里的動態模塊加載
declare const System: any;

import { ZipCodeValidator as Zip } from "./ZipCodeValidator";

if (needZipValidation) {
    System.import("./ZipCodeValidator").then((ZipCodeValidator: typeof Zip) => {
        var x = new ZipCodeValidator();
        if (x.isAcceptable("...")) { /* ... */ }
    });
}

使用其它的JavaScript庫

要想描述非 TypeScript 編寫的類庫的類型,我們需要聲明類庫所暴露出的 API。

我們叫它聲明因為它不是 “外部程序” 的具體實現。 它們通常是在 .d.ts文件里定義的。 如果你熟悉 C/C++,你可以把它們當做 .h文件。 讓我們看一些例子。

外部模塊

注釋:module 后的名稱在 TS 中進行了全局注入。

在 Node.js 里大部分工作是通過加載一個或多個模塊實現的。 我們可以使用頂級的 export聲明來為每個模塊都定義一個.d.ts文件,但最好還是寫在一個大的.d.ts文件里。 我們使用與構造一個外部命名空間相似的方法,但是這里使用 module關鍵字並且把名字用引號括起來,方便之后import。 例如:

node.d.ts (simplified excerpt)

注釋:這里不是一個全局注冊,他同樣需要引用才能使用 url,只是這種寫法不會出現 url 的引用提醒,只會出現它包含的 Url 接口的。等同於定義了一個內部模塊,這個模塊不被導出,只有模塊內的方法或屬性被導出了。
注釋:這里是一個 .d.ts 文件,這里的function parse不是標准類型寫法,在 .ts 中會報缺乏實體定義。下文的let sep也相同,應該可以理解為在 .d.ts 文件中可以書寫實體,但是不需要寫實體的實現。

declare module "url" {
    export interface Url {
        protocol?: string;
        hostname?: string;
        pathname?: string;
    }

    export function parse(urlStr: string, parseQueryString?, slashesDenoteHost?): Url;
}

declare module "path" {
    export function normalize(p: string): string;
    export function join(...paths: any[]): string;
    export let sep: string;
}

注釋:下文的<reference>標簽用於標明當前模塊的依賴模塊
現在我們可以/// <reference> node.d.ts並且使用import url = require("url");import * as URL from "url"加載模塊。

/// <reference path="node.d.ts"/>
import * as URL from "url";
let myUrl = URL.parse("http://www.typescriptlang.org");

外部模塊簡寫

假如你不想在使用一個新模塊之前花時間去編寫聲明,你可以采用聲明的簡寫形式以便能夠快速使用它。

declarations.d.ts

注釋:當模塊名是個字符串時,指明該模塊是用來聲明字符串匹配的模塊的。后文中提到這個這個字符串可以使用通配符。

declare module "hot-new-module";

簡寫模塊里所有導出的類型將是any

import x, {y} from "hot-new-module";
x(y);

模塊聲明通配符

某些模塊加載器如 SystemJSAMD 支持導入非 JavaScript 內容。 它們通常會使用一個前綴或后綴來表示特殊的加載語法。 模塊聲明通配符可以用來表示這些情況。

注釋:通配符可以實現對一個類型的模塊進行統一的類型定義
蛛絲:模塊內可以寫 expor default 作為整個模塊的導出名

declare module "*!text" {
    const content: string;
    export default content;
}
// Some do it the other way around.
declare module "json!*" {
    const value: any;
    export default value;
}

現在你可以就導入匹配"*!text""json!*"的內容了。

import fileContent from "./xyz.txt!text";
import data from "json!http://example.com/data.json";
console.log(data, fileContent);

UMD 模塊

有些模塊被設計成兼容多個模塊加載器,或者不使用模塊加載器(全局變量)。 它們以 UMD 模塊為代表。 這些庫可以通過導入的形式或全局變量的形式訪問。 例如:

注釋:export as 使模塊不僅能夠被導入使用,也提供了全局注冊使用的方式,但是這兩個方式之前並不兼容。可以理解為函數的重載,只是一個模塊有兩種使用方式

math-lib.d.ts
export function isPrime(x: number): boolean;
export as namespace mathLib;

之后,這個庫可以在某個模塊里通過導入來使用:

import { isPrime } from "math-lib";
isPrime(2);
mathLib.isPrime(2); // 錯誤: 不能在模塊內使用全局定義。

它同樣可以通過全局變量的形式使用,但只能在某個腳本(指不帶有模塊導入或導出的腳本文件)里。

mathLib.isPrime(2);

創建模塊結構指導

盡可能地在頂層導出

從你的模塊中導出一個命名空間就是一個增加嵌套的例子。

使用重新導出進行擴展

模塊不會像全局命名空間對象那樣去 合並

模塊里不要使用命名空間

模塊具有其自己的作用域,並且只有導出的聲明才會在模塊外部可見。 記住這點,命名空間在使用模塊時幾乎沒什么價值。

模塊本身已經存在於文件系統之中,這是必須的。 我們必須通過路徑和文件名找到它們,這已經提供了一種邏輯上的組織形式。 我們可以創建 /collections/generic/文件夾,把相應模塊放在這里面。

命名空間對解決全局作用域里命名沖突來說是很重要的。 比如,你可以有一個 My.Application.Customer.AddFormMy.Application.Order.AddForm -- 兩個類型的名字相同,但命名空間不同。

更多關於模塊和命名空間的資料查看命名空間和模塊

危險信號

以下均為模塊結構上的危險信號。重新檢查以確保你沒有在對模塊使用命名空間:

  • 文件的頂層聲明是export namespace Foo { ... } (刪除Foo並把所有內容向上層移動一層)
  • 文件只有一個export classexport function (考慮使用export default
  • 多個文件的頂層具有同樣的export namespace Foo { (不要以為這些會合並到一個Foo中!)

注釋:在 4.1.2 中,兩個export的命名空間會自動合並

——————————————————————————————————————————————————————

以下內容來源於 聲明合並 原文地址 www.tslang.cn

模塊擴展

注釋:被拓展的目標必須是該目標的定義所在,像export * from "./StringValidator";這樣的重新導出,不存在該目標的定義是不能夠拓展的。

雖然 JavaScript 不支持合並,但你可以為導入的對象打補丁以更新它們。讓我們考察一下這個玩具性的示例:

// observable.js
export class Observable<T> {
    // ... implementation left as an exercise for the reader ...
}

// map.js
import { Observable } from "./observable";
Observable.prototype.map = function (f) {
    // ... another exercise for the reader
}

它也可以很好地工作在 TypeScript 中, 但編譯器對 Observable.prototype.map一無所知。 你可以使用擴展模塊來將它告訴編譯器:

// observable.ts stays the same
// map.ts
import { Observable } from "./observable";
declare module "./observable" {
    interface Observable<T> {
        map<U>(f: (x: T) => U): Observable<U>;
    }
}
Observable.prototype.map = function (f) {
    // ... another exercise for the reader
}


// consumer.ts
import { Observable } from "./observable";
import "./map";
let o: Observable<number>;
o.map(x => x.toFixed());

模塊名的解析和用 import/ export解析模塊標識符的方式是一致的。 更多信息請參考 Modules。 當這些聲明在擴展中合並時,就好像在原始位置被聲明了一樣。 但是,你不能在擴展中聲明新的頂級聲明-僅可以擴展模塊中已經存在的聲明。

全局擴展

你也以在模塊內部添加聲明到全局作用域中。

// observable.ts
export class Observable<T> {
    // ... still no implementation ...
}

declare global {
    interface Array<T> {
        toObservable(): Observable<T>;
    }
}

Array.prototype.toObservable = function () {
    // ...
}

全局擴展與模塊擴展的行為和限制是相同的。

——————————————————————————————————————————————————————

以下內容來源於 命名空間和模塊 原文地址 www.tslang.cn

模塊的取舍

就像每個 JS 文件對應一個模塊一樣,TypeScript 里模塊文件與生成的 JS 文件也是一一對應的。 這會產生一種影響,根據你指定的目標模塊系統的不同,你可能無法連接多個模塊源文件。 例如當目標模塊系統為 commonjsumd時,無法使用outFile選項,但是在 TypeScript 1.8 以上的版本能夠使用outFile當目標為amdsystem

注釋:UMD 是 AMD、CommonJs、CMD 的融合寫法,使一個模塊能同時被三種加載方式加載
注釋:CMD 和 AMD 的不同在於 CMD 在需要依賴時執行依賴,而 AMD 的依賴前置到模塊前。Seajs 是 CMD 的實現。
疑問:UMD 的實現是哪個庫?system 是一種新的模塊加載方案?和 system.js 是什么關系?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM