JavaScript 長期以來都有不同的方式來處理模組化程式碼。TypeScript 自 2012 年推出以來,已實作對許多這些格式的支援,但隨著時間的推移,社群和 JavaScript 規格已收斂到稱為 ES 模組(或 ES6 模組)的格式。您可能知道它是 import
/export
語法。
ES 模組於 2015 年新增至 JavaScript 規格,並於 2020 年獲得大多數網路瀏覽器和 JavaScript 執行時間的廣泛支援。
為了專注,手冊將涵蓋 ES 模組及其廣受歡迎的前身 CommonJS module.exports =
語法,您可以在 模組 的參考部分中找到有關其他模組模式的資訊。
JavaScript 模組的定義方式
在 TypeScript 中,就像在 ECMAScript 2015 中一樣,任何包含頂層 import
或 export
的檔案都被視為模組。
相反地,沒有任何頂層匯入或匯出宣告的檔案會被視為腳本,其內容可於全域範圍內使用(因此模組也可以使用)。
模組會在它們自己的範圍內執行,而不是在全域範圍內執行。這表示在模組中宣告的變數、函式、類別等在模組外不可見,除非它們使用其中一種匯出形式明確地匯出。相反地,要使用從不同模組匯出的變數、函式、類別、介面等,必須使用其中一種匯入形式匯入。
非模組
在我們開始之前,了解 TypeScript 認為什麼是模組非常重要。JavaScript 規範宣告任何沒有 import
宣告、export
或頂層 await
的 JavaScript 檔案都應該視為腳本,而非模組。
在腳本檔案內,變數和類型宣告為在共用全域範圍內,並且假設您會使用 outFile
編譯器選項將多個輸入檔案合併成一個輸出檔案,或在 HTML 中使用多個 <script>
標籤載入這些檔案(順序正確!)。
如果您有一個目前沒有任何 import
或 export
的檔案,但您想要將其視為模組,請新增一行
tsTry
export {};
這將會將檔案變更為一個不輸出任何內容的模組。此語法不論您的模組目標為何,都能運作。
TypeScript 中的模組
其他閱讀資料
急躁的 JS(模組)
MDN:JavaScript 模組
在 TypeScript 中撰寫基於模組的程式碼時,有三個主要事項需要考量
- 語法:我想使用什麼語法來匯入和匯出項目?
- 模組解析:模組名稱(或路徑)與磁碟上的檔案之間的關係為何?
- 模組輸出目標:我發出的 JavaScript 模組應該是什麼樣子?
ES 模組語法
檔案可透過 export default
宣告主要匯出
tsTry
// @filename: hello.tsexport default functionhelloWorld () {console .log ("Hello, world!");}
這會透過以下方式匯入
tsTry
importhelloWorld from "./hello.js";helloWorld ();
除了預設匯出之外,您還可以透過 export
匯出多個變數和函式,方法是省略 default
tsTry
// @filename: maths.tsexport varpi = 3.14;export letsquareTwo = 1.41;export constphi = 1.61;export classRandomNumberGenerator {}export functionabsolute (num : number) {if (num < 0) returnnum * -1;returnnum ;}
這些可以在另一個檔案中透過 import
語法使用
tsTry
import {pi ,phi ,absolute } from "./maths.js";console .log (pi );constabsPhi =absolute (phi );
其他匯入語法
匯入可以使用 import {old as new}
格式重新命名
tsTry
import {pi asπ } from "./maths.js";console .log (π );
您可以將上述語法混合搭配到單一 import
中
tsTry
// @filename: maths.tsexport constpi = 3.14;export default classRandomNumberGenerator {}// @filename: app.tsimportRandomNumberGenerator , {pi asπ } from "./maths.js";RandomNumberGenerator ;console .log (π );
您可以使用 * as name
將所有匯出的物件放入單一命名空間中
tsTry
// @filename: app.tsimport * asmath from "./maths.js";console .log (math .pi );constpositivePhi =math .absolute (math .phi );
您可以透過 import "./file"
匯入檔案,不將任何變數納入目前的模組中
tsTry
// @filename: app.tsimport "./maths.js";console .log ("3.14");
在此情況下,import
什麼都不做。不過,maths.ts
中的所有程式碼都已評估,這可能會觸發影響其他物件的副作用。
TypeScript 特定的 ES 模組語法
類型可以使用與 JavaScript 值相同的語法匯出和匯入
tsTry
// @filename: animal.tsexport typeCat = {breed : string;yearOfBirth : number };export interfaceDog {breeds : string[];yearOfBirth : number;}// @filename: app.tsimport {Cat ,Dog } from "./animal.js";typeAnimals =Cat |Dog ;
TypeScript 已擴充 import
語法,新增兩個用於宣告類型匯入的概念
import type
這是一個匯入陳述式,只能匯入類型
tsTry
// @filename: animal.tsexport type'createCatName' cannot be used as a value because it was imported using 'import type'.1361'createCatName' cannot be used as a value because it was imported using 'import type'.Cat = {breed : string;yearOfBirth : number };export typeDog = {breeds : string[];yearOfBirth : number };export constcreateCatName = () => "fluffy";// @filename: valid.tsimport type {Cat ,Dog } from "./animal.js";export typeAnimals =Cat |Dog ;// @filename: app.tsimport type {createCatName } from "./animal.js";constname =createCatName ();
內嵌 type
匯入
TypeScript 4.5 也允許個別匯入加上 type
前綴,以表示匯入的參考為類型
tsTry
// @filename: app.tsimport {createCatName , typeCat , typeDog } from "./animal.js";export typeAnimals =Cat |Dog ;constname =createCatName ();
這些功能加起來,讓非 TypeScript 轉譯器(例如 Babel、swc 或 esbuild)知道哪些匯入可以安全移除。
具備 CommonJS 行為的 ES 模組語法
TypeScript 具有 ES 模組語法,直接對應到 CommonJS 和 AMD 的 require
。使用 ES 模組的匯入在大部分情況下與這些環境中的 require
相同,但此語法可確保 TypeScript 檔案與 CommonJS 輸出之間有 1 對 1 的對應關係
tsTry
importfs = require("fs");constcode =fs .readFileSync ("hello.ts", "utf8");
您可以在 模組參考頁面 中深入了解此語法。
CommonJS 語法
CommonJS 是 npm 上大多數模組所提供的格式。即使您使用上述 ES 模組語法撰寫,了解 CommonJS 語法如何運作有助於您更輕鬆地除錯。
匯出
識別碼會透過設定名為 module
的全域變數上的 exports
屬性來匯出。
tsTry
functionabsolute (num : number) {if (num < 0) returnnum * -1;returnnum ;}module .exports = {pi : 3.14,squareTwo : 1.41,phi : 1.61,absolute ,};
然後,這些檔案可以透過 require
陳述式匯入
tsTry
constmaths =require ("./maths");maths .pi ;
或者,您可以使用 JavaScript 中的解構功能簡化一點
tsTry
const {squareTwo } =require ("./maths");squareTwo ;
CommonJS 和 ES 模組互通
CommonJS 和 ES 模組在預設匯入和模組命名空間物件匯入的區別上,功能不匹配。TypeScript 有個編譯器標記,可使用 esModuleInterop
減少兩組不同限制之間的摩擦。
TypeScript 的模組解析選項
模組解析是從 import
或 require
陳述式中取得字串,並決定該字串所指的文件。
TypeScript 包含兩種解析策略:傳統和 Node。傳統是預設值,當編譯器選項 module
不是 commonjs
時,會包含傳統策略以維持向後相容性。Node 策略複製了 Node.js 在 CommonJS 模式下的運作方式,並針對 .ts
和 .d.ts
進行額外檢查。
有許多 TSConfig 標記會影響 TypeScript 中的模組策略: moduleResolution
、baseUrl
、paths
、rootDirs
。
有關這些策略如何運作的完整詳細資料,您可以參閱模組解析度參考頁面。
TypeScript 的模組輸出選項
有兩個選項會影響發出的 JavaScript 輸出
您使用的target
由您預期執行 TypeScript 程式碼的 JavaScript 執行環境中的可用功能決定。這可能是:您支援的最舊網路瀏覽器、您預期執行的 Node.js 的最低版本,或來自您的執行環境的獨特限制(例如 Electron)。
模組之間的所有通訊都透過模組載入器進行,編譯器選項module
決定使用哪一個。在執行階段,模組載入器負責在執行模組之前找到並執行模組的所有相依性。
例如,以下是使用 ES 模組語法的 TypeScript 檔案,展示了幾個不同的 module
選項
tsTry
import {valueOfPi } from "./constants.js";export consttwoPi =valueOfPi * 2;
ES2020
tsTry
import { valueOfPi } from "./constants.js";export const twoPi = valueOfPi * 2;
CommonJS
tsTry
"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.twoPi = void 0;const constants_js_1 = require("./constants.js");exports.twoPi = constants_js_1.valueOfPi * 2;
UMD
tsTry
(function (factory) {if (typeof module === "object" && typeof module.exports === "object") {var v = factory(require, exports);if (v !== undefined) module.exports = v;}else if (typeof define === "function" && define.amd) {define(["require", "exports", "./constants.js"], factory);}})(function (require, exports) {"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.twoPi = void 0;const constants_js_1 = require("./constants.js");exports.twoPi = constants_js_1.valueOfPi * 2;});
請注意,ES2020 與原始的
index.ts
實際上相同。
您可以在 module
的 TSConfig 參考 中看到所有可用的選項及其發出的 JavaScript 程式碼的外觀。
TypeScript 命名空間
TypeScript 有其自己的模組格式,稱為 命名空間
,它早於 ES 模組標準。此語法有許多有用的功能,可用於建立複雜的定義檔案,而且在 DefinitelyTyped 中仍然活躍使用。雖然尚未棄用,但命名空間中的大部分功能都存在於 ES 模組中,我們建議您使用它來與 JavaScript 的方向一致。您可以在 命名空間參考頁面 中瞭解更多關於命名空間的資訊。