PJCHENder 未整理筆記

[TS] Namespaces and Modules

2020-07-03

[TS] Namespaces and Modules

TL;DR

1
2
3
4
5
6
7
// re-export
export * from 'types/device';
export { default } from 'components/Card';
export { default as ResidentCard } from './ResidentCard';

// make file as modules
export {};

此篇為各筆記之整理,非原創內容,資料來源可見下方連結與文後參考資料:

在 TypeScript 1.5 以後,原本的 internal modules 改名為 namespaces;原本的 external modules 改名為 modules

在 TypeScript 中,只要檔案中有使用到 importexport,則該檔案即會被視為 module;但若沒有,則會套用 TypeScript 中 namespaces 個概念,這將會導致不同檔案間同名稱的變數有衝突的情況。要解決這個問題,只需要在檔案的最上放加上 export {}; 讓這支檔案被當成 module 來處理即可

Modules

在 Modules 中可以同時包含程式碼(code)和宣告(declaration)。Modules 的使用需要依靠 module loader(例如,CommonJs/Require.js)或其他支援 ES Modules 的執行環境(runtime)。在當今的 Node.js 應用程式中,相較於 namespaces 的用法,modules 是我們更推薦的使用方式。

如同 ES6,在 TypeScript 中任何包含 importexport 的檔案都被視為 module,相反的若沒有使用到 importexport 則被視為 script,這裡面的變數會暴露在全域。

1
2
3
4
5
6
7
8
export interface StringValidator {
isAcceptable(s: string): boolean;
}

// 函式和 Class 的語法可以直接接在 `export default` 後而不需要特別命名。
export default function(s: string) {
return s.length === 5 && numberRegexp.test(s);
}

import type

在匯入型別(type)時,原本是使用 import,在 TypeScript 3.8 之後,還可以使用 import type 語法:

1
2
3
4
5
// Explicitly use import type
import type {APIResponseType} from "./api";

// Re-using the same import
import {APIResponseType} from "./api";

default exports

使用 export default 匯出:

1
2
3
// pkg.ts
const foo = 'foo';
export default foo;

使用 import <name> from <path> 匯入:

1
2
3
// main.ts
import bar from './pkg'
console.log(bar); // foo

export = 和 import = require() 語法

在 TS 中還有一種特殊的 export =import = require() 的用法,這兩種必須對應使用:

使用 export = 來匯出:

1
2
3
// pkg.ts
const foo = 'foo';
export = foo;

使用 import <name> = require('<path>') 來匯入:

1
2
3
// main.ts
import bar = require('./pkg');
console.log(bar); // foo

動態載入(dynamic/optional module loading)

Optional Module Loading and Other Advanced Loading Scenarios @ TypeScript

重新匯出(re-exports)

使用 export * from <path> 可以直接把某一個模組重新匯出,而不會改變原有的內容(not mutate):

1
2
3
// foo.ts
export * from "./ParseIntBasedZipCodeValidator";
export * as utilities from "./utilities";

Ambient Modules

keywords: d.ts

撰寫第三方套件時,若想要讓 TypeScript 知道套件的樣貌,需要清楚定義套件所公開的 API,在 TypeScript 中,把這種只有定義而沒有實際操作內容的部分稱作 Ambient,通常會以 .d.ts 的副檔名作為結尾。

這裡可以使用 declare 搭配 module 這兩個關鍵字來完成,也就是 declare module "<module_name>"

1
2
3
4
5
6
7
8
// pkg.ts
declare module 'pkg' {
export interface SomeType {
foo: string;
bar: number;
}
export function greet(name: string): void;
}

正常匯入:

1
2
3
4
5
6
import * as PKG from 'pkg';

const foobar: PKG.SomeType = {
foo: 'foo',
bar: 2,
};

結構化 module 的建議

  • 以靠近最上層的方式匯出(exporting near the top-level),因為 modules 本身就已經有自己的 scope,因此盡可能不要有(用)額外的 namespaces,透過檔名本身就已經有分類的作用。
  • 如果只是要匯出「一個」 function 或 class,直接使用 export default
1
2
3
export default function greet() {
return "Hello, TypeScript";
}
  • 如果需要匯出多個物件,將他們都放到最上層
1
2
3
4
5
export const foo = 'foo';

export function greet() {
return "Hello, TypeScript";
}
  • 清楚列出所有匯入 module 的名稱:
1
import { foo, greet } from './pkg';
  • 如果需要一次匯入非常多東西,則是用 namespace 的方式來匯入
1
2
import * as myLargeModule from "./MyLargeModule.ts";
// myLargeModule.foo

Namespaces

Namespaces 是 TypeScript 獨特組織程式碼的方式。它們其實就是在 global namespace 下的 JavaScript 物件。和 Modules 不同,Namespaces 可以橫跨多個檔案,同時透過 --outFile 來加以連接,因此即使在不同檔案中,只要 namespaces 相同,都可以被提取,只需要使用:

1
/// <reference path="Validation.ts" />

讓 TS 知道這隻檔案的 namespace 同時在其他檔案中。

最後,在編譯時,只需要加上 --outFile 的標籤:

1
$ tsc --outFile sample.js Test.ts

掃描二維條碼,分享此文章