1. 程式人生 > >TypeScript基礎入門之模組(五)

TypeScript基礎入門之模組(五)

構建模組的指南

匯出儘可能接近頂級

使用您匯出的東西時,模組的消費者應儘可能少地摩擦。 新增太多級別的巢狀往往很麻煩,因此請仔細考慮如何構建事物。

從模組匯出名稱空間是新增太多巢狀層的示例。 雖然名稱空間有時會有用,但在使用模組時會增加額外的間接級別。 這很快就會成為使用者的痛點,而且通常是不必要的。

匯出類上的靜態方法也有類似的問題 - 類本身會新增一層巢狀。 除非以明顯有用的方式增加表達性或意圖,否則請考慮簡單地匯出輔助函式。

如果您只匯出單個類或函式,請使用export default

正如"頂級附近的出口"減少了模組消費者的摩擦,引入預設匯出也是如此。 如果模組的主要用途是容納一個特定的匯出,那麼您應該考慮將其匯出為預設匯出。 這使匯入和實際使用匯入更容易一些。 例如:

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();

如果要匯入大量內容,請使用名稱空間匯入模式

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();

重新匯出擴充套件延伸

通常,您需要擴充套件模組的功能。 一個常見的JS模式是使用擴充套件來擴充原始物件,類似於JQuery擴充套件的工作方式。 正如我們之前提到的,模組不像全域性名稱空間物件那樣合併。 建議的解決方案是不改變原始物件,而是匯出提供新功能的新實體。 考慮模組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()}'`);
}

這是使用暴露測試功能的計算器的簡單測試。

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


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

現在擴充套件這個以新增對10以外基數的輸入的支援,讓我們建立ProgrammerCalculator.tsProgrammerCalculator.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匯出類似於原始Calculator模組的API形狀,但不會擴充原始模組中的任何物件。 這是我們的ProgrammerCalculator類的測試:TestProgrammerCalculator.ts

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

let c = new Calculator(2);
test(c, "001+010="); // prints 3

不要在模組中使用名稱空間

首次遷移到基於模組的組織時,常見的趨勢是將匯出包裝在額外的名稱空間層中。 模組有自己的範圍,只有模組外部才能看到匯出的宣告。 考慮到這一點,在使用模組時,名稱空間提供的值很小(如果有的話)。

在組織方面,名稱空間可以方便地將全域性範圍內與邏輯相關的物件和型別組合在一起。 例如,在C#中,您將在System.Collections中找到所有集合型別。 通過將我們的型別組織成分層名稱空間,我們為這些型別的使用者提供了良好的“發現”體驗。 另一方面,模組必然存在於檔案系統中。 我們必須通過路徑和檔名來解決它們,因此我們可以使用邏輯組織方案。 我們可以在 /collections/generic/資料夾中包含一個列表模組。

名稱空間對於避免在全域性範圍內命名衝突很重要。 例如,您可能擁有My.Application.Customer.AddForm和My.Application.Order.AddForm - 兩個具有相同名稱但具有不同名稱空間的型別。 然而,這不是模組的問題。 在一個模組中,沒有合理的理由讓兩個具有相同名稱的物件。 從消費方面來看,任何給定模組的消費者都會選擇他們用來引用模組的名稱,因此不可能發生意外命名衝突。

有關模組和名稱空間的更多討論,請參閱名稱空間和模組。

以下所有內容都是模組結構的紅色標誌。如果其中任何一個適用於您的檔案,請仔細檢查您是否嘗試命名外部模組:

1. 一個檔案,其唯一的頂級宣告是匯出名稱空間Foo {...}(刪除Foo並將所有內容"向上移動"一個級別) 2. 具有單個匯出類或匯出功能的檔案(請考慮使用匯出預設值) 3. 具有相同```export namespace Foo {```的多個檔案在頂層(不要認為這些將組合成一個Foo!)