TypeScript——模塊(3)

創(chuàng)建模塊結(jié)構(gòu)指導

盡可能地在頂層導出

用戶應該更容易地使用你模塊導出的內(nèi)容钠绍。 嵌套層次過多會變得難以處理,因此仔細考慮一下如何組織你的代碼躬存。

從你的模塊中導出一個命名空間就是一個增加嵌套的例子。 雖然命名空間有時候有它們的用處,在使用模塊的時候它們額外地增加了一層圾笨。 這對用戶來說是很不便的并且通常是多余的。

導出類的靜態(tài)方法也有同樣的問題 - 這個類本身就增加了一層嵌套逊谋。 除非它能方便表述或便于清晰使用擂达,否則請考慮直接導出一個輔助方法。

如果僅導出單個 class 或 function胶滋,使用 export default

就像“在頂層上導出”幫助減少用戶使用的難度板鬓,一個默認的導出也能起到這個效果。 如果一個模塊就是為了導出特定的內(nèi)容究恤,那么你應該考慮使用一個默認導出俭令。 這會令模塊的導入和使用變得些許簡單。 比如:

MyClass.ts

export default class SomeType {

? constructor() { ... }

}

MyFunc.ts

export default function getThing() { return 'thing'; }

Consumer.ts

import t from "./MyClass";

import f from "./MyFunc";

let x = new t();

console.log(f());

對用戶來說這是最理想的部宿。他們可以隨意命名導入模塊的類型(本例為t)并且不需要多余的(.)來找到相關對象抄腔。

如果要導出多個對象,把它們放在頂層里導出

MyThings.ts

export class SomeType { /* ... */ }

export function someFunc() { /* ... */ }

相反地理张,當導入的時候:

明確地列出導入的名字

Consumer.ts

import { SomeType, someFunc } from "./MyThings";

let x = new SomeType();

let y = someFunc();

使用命名空間導入模式當你要導出大量內(nèi)容的時候

MyLargeModule.ts

export class Dog { ... }

export class Cat { ... }

export class Tree { ... }

export class Flower { ... }

Consumer.ts

import * as myLargeModule from "./MyLargeModule.ts";

let x = new myLargeModule.Dog();

使用重新導出進行擴展

你可能經(jīng)常需要去擴展一個模塊的功能赫蛇。 JS里常用的一個模式是JQuery那樣去擴展原對象。 如我們之前提到的涯穷,模塊不會像全局命名空間對象那樣去 合并棍掐。 推薦的方案是 不要去改變原來的對象,而是導出一個新的實體來提供新的功能拷况。

假設Calculator.ts模塊里定義了一個簡單的計算器實現(xiàn)作煌。 這個模塊同樣提供了一個輔助函數(shù)來測試計算器的功能掘殴,通過傳入一系列輸入的字符串并在最后給出結(jié)果。

Calculator.ts

export class Calculator {

? ? private current = 0;

? ? private memory = 0;

? ? private operator: string;

? ? protected processDigit(digit: string, currentValue: number) {

? ? ? ? if (digit >= "0" && digit <= "9") {

? ? ? ? ? ? return currentValue * 10 + (digit.charCodeAt(0) - "0".charCodeAt(0));

? ? ? ? }

? ? }

? ? protected processOperator(operator: string) {

? ? ? ? if (["+", "-", "*", "/"].indexOf(operator) >= 0) {

? ? ? ? ? ? return operator;

? ? ? ? }

? ? }

? ? protected evaluateOperator(operator: string, left: number, right: number): number {

? ? ? ? switch (this.operator) {

? ? ? ? ? ? case "+": return left + right;

? ? ? ? ? ? case "-": return left - right;

? ? ? ? ? ? case "*": return left * right;

? ? ? ? ? ? case "/": return left / right;

? ? ? ? }

? ? }

? ? private evaluate() {

? ? ? ? if (this.operator) {

? ? ? ? ? ? this.memory = this.evaluateOperator(this.operator, this.memory, this.current);

? ? ? ? }

? ? ? ? else {

? ? ? ? ? ? this.memory = this.current;

? ? ? ? }

? ? ? ? this.current = 0;

? ? }

? ? public handleChar(char: string) {

? ? ? ? if (char === "=") {

? ? ? ? ? ? this.evaluate();

? ? ? ? ? ? return;

? ? ? ? }

? ? ? ? else {

? ? ? ? ? ? let value = this.processDigit(char, this.current);

? ? ? ? ? ? if (value !== undefined) {

? ? ? ? ? ? ? ? this.current = value;

? ? ? ? ? ? ? ? return;

? ? ? ? ? ? }

? ? ? ? ? ? else {

? ? ? ? ? ? ? ? let value = this.processOperator(char);

? ? ? ? ? ? ? ? if (value !== undefined) {

? ? ? ? ? ? ? ? ? ? this.evaluate();

? ? ? ? ? ? ? ? ? ? this.operator = value;

? ? ? ? ? ? ? ? ? ? return;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? throw new Error(`Unsupported input: '${char}'`);

? ? }

? ? public getResult() {

? ? ? ? return this.memory;

? ? }

}

export function test(c: Calculator, input: string) {

? ? for (let i = 0; i < input.length; i++) {

? ? ? ? c.handleChar(input[i]);

? ? }

? ? console.log(`result of '${input}' is '${c.getResult()}'`);

}

下面使用導出的test函數(shù)來測試計算器粟誓。

TestCalculator.ts

import { Calculator, test } from "./Calculator";

let c = new Calculator();

test(c, "1+2*33/11="); // prints 9

現(xiàn)在擴展它奏寨,添加支持輸入其它進制(十進制以外),讓我們來創(chuàng)建ProgrammerCalculator.ts鹰服。

ProgrammerCalculator.ts

import { Calculator } from "./Calculator";

class ProgrammerCalculator extends Calculator {

? ? static digits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];

? ? constructor(public base: number) {

? ? ? ? super();

? ? ? ? const maxBase = ProgrammerCalculator.digits.length;

? ? ? ? if (base <= 0 || base > maxBase) {

? ? ? ? ? ? throw new Error(`base has to be within 0 to ${maxBase} inclusive.`);

? ? ? ? }

? ? }

? ? protected processDigit(digit: string, currentValue: number) {

? ? ? ? if (ProgrammerCalculator.digits.indexOf(digit) >= 0) {

? ? ? ? ? ? return currentValue * this.base + ProgrammerCalculator.digits.indexOf(digit);

? ? ? ? }

? ? }

}

// Export the new extended calculator as Calculator

export { ProgrammerCalculator as Calculator };

// Also, export the helper function

export { test } from "./Calculator";

新的ProgrammerCalculator模塊導出的API與原先的Calculator模塊很相似病瞳,但卻沒有改變原模塊里的對象。 下面是測試ProgrammerCalculator類的代碼:

TestProgrammerCalculator.ts

import { Calculator, test } from "./ProgrammerCalculator";

let c = new Calculator(2);

test(c, "001+010="); // prints 3

模塊里不要使用命名空間

當初次進入基于模塊的開發(fā)模式時悲酷,可能總會控制不住要將導出包裹在一個命名空間里套菜。 模塊具有其自己的作用域,并且只有導出的聲明才會在模塊外部可見设易。 記住這點逗柴,命名空間在使用模塊時幾乎沒什么價值。

在組織方面顿肺,命名空間對于在全局作用域內(nèi)對邏輯上相關的對象和類型進行分組是很便利的戏溺。 例如,在C#里屠尊,你會從 System.Collections里找到所有集合的類型旷祸。 通過將類型有層次地組織在命名空間里,可以方便用戶找到與使用那些類型讼昆。 然而托享,模塊本身已經(jīng)存在于文件系統(tǒng)之中,這是必須的浸赫。 我們必須通過路徑和文件名找到它們嫌吠,這已經(jīng)提供了一種邏輯上的組織形式。 我們可以創(chuàng)建 /collections/generic/文件夾掺炭,把相應模塊放在這里面辫诅。

命名空間對解決全局作用域里命名沖突來說是很重要的。 比如涧狮,你可以有一個 My.Application.Customer.AddForm和My.Application.Order.AddForm -- 兩個類型的名字相同炕矮,但命名空間不同。 然而者冤,這對于模塊來說卻不是一個問題肤视。 在一個模塊里,沒有理由兩個對象擁有同一個名字涉枫。 從模塊的使用角度來說邢滑,使用者會挑出他們用來引用模塊的名字,所以也沒有理由發(fā)生重名的情況愿汰。

危險信號

以下均為模塊結(jié)構(gòu)上的危險信號困后。重新檢查以確保你沒有在對模塊使用命名空間:

文件的頂層聲明是export namespace Foo { ... } (刪除Foo并把所有內(nèi)容向上層移動一層)

文件只有一個export class或export function (考慮使用export default)

多個文件的頂層具有同樣的export namespace Foo { (不要以為這些會合并到一個Foo中@种健)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市摇予,隨后出現(xiàn)的幾起案子汽绢,更是在濱河造成了極大的恐慌,老刑警劉巖侧戴,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宁昭,死亡現(xiàn)場離奇詭異,居然都是意外死亡酗宋,警方通過查閱死者的電腦和手機积仗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜕猫,“玉大人斥扛,你說我怎么就攤上這事〉で拢” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵芬失,是天一觀的道長楣黍。 經(jīng)常有香客問我,道長棱烂,這世上最難降的妖魔是什么租漂? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮颊糜,結(jié)果婚禮上哩治,老公的妹妹穿的比我還像新娘。我一直安慰自己衬鱼,他們只是感情好业筏,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鸟赫,像睡著了一般蒜胖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上抛蚤,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天台谢,我揣著相機與錄音,去河邊找鬼岁经。 笑死朋沮,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的缀壤。 我是一名探鬼主播樊拓,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼纠亚,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了骑脱?” 一聲冷哼從身側(cè)響起菜枷,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎叁丧,沒想到半個月后啤誊,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡拥娄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年蚊锹,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片稚瘾。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡牡昆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出摊欠,到底是詐尸還是另有隱情丢烘,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布些椒,位于F島的核電站播瞳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏免糕。R本人自食惡果不足惜赢乓,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望石窑。 院中可真熱鬧牌芋,春花似錦、人聲如沸松逊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽经宏。三九已至楼咳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間烛恤,已是汗流浹背母怜。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留缚柏,地道東北人苹熏。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親轨域。 傳聞我的和親對象是個殘疾皇子袱耽,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容