1. 程式人生 > >Typescript學習筆記(五) 模組機制

Typescript學習筆記(五) 模組機制

javascript從es5之前都缺少一種模組機制,無法通過js引入檔案,於是requirejs等等的載入器應運而生。這些載入器的使用也並不統一,產生了amd,commonjs,umd等等的規範,各有所長,直到es6的釋出,js自身引入的模組機制,將會在以後逐漸被應用起來。

Typescrit的模組機制與es6的模組基本類似,也提供了轉換為amd,es6,umd,commonjs,system的轉換,一會我們通過一個簡單的例子來執行一下ts的模組。

ts特殊的內聯標籤。

/// <reference path="revence.ts" /> 

三個斜線,reference標籤,path指向引入的檔案,引入的檔案呢,需要定義一個名稱空間來引用定義的東西。

revence.ts

 namespace Validation {
    const ac = "abc";
    export  function a(){
        console.log("module be loaded");
    }
}

定義一個名為Validation的名稱空間,匯出用export匯出。這裡Validation下的a函式被匯出,引用的時候就可以用Validation.a()來引用它。

text.ts

/// <reference path="revence.ts" /> 
Validation.a();

main檔案是這個text.ts,引用了revence檔案。編譯器怎麼編譯它呢?

兩種方法:一,把所有的輸入檔案編譯為一個輸出檔案,需要使用--outFile標記:

tsc text.ts --outFile a.js  

這樣輸出檔案都會在a.js中,網頁script引入了。

    二,我們可以編譯每一個檔案(預設方式),那麼每個原始檔都會對應生成一個JavaScript檔案。 然後,在頁面上通過<script>標籤把所有生成的JavaScript檔案按正確的順序引進來,比如:

tsc --outFile sample.js Validation.ts LettersOnlyValidator.ts ZipCodeValidator.ts Test.ts
   <script src="Validation.js" type="text/javascript" />
    <script src="LettersOnlyValidator.js" type="text/javascript" />
    <script src="ZipCodeValidator.js" type="text/javascript" />
    <script src="Test.js" type="text/javascript" />

接下來就是ts的模組機制,而非script載入機制了。

關鍵詞:import,export。同es6。

通過export關鍵詞匯出,包括interface的任何東西都會被匯出。比如

export const numberRegexp = /^[0-9]+$/;
export interface StringValidator {
    isAcceptable(s: string): boolean;
}
export class ParseIntBasedZipCodeValidator {
    isAcceptable(s: string) {
        return s.length === 5 && parseInt(s).toString() === s;
    }
}

不用關心匯出內容是什麼,反正通過export已把這些東西都匯出了。

1。重新匯出。as相當於重新命名,from之後的是匯入的地址,這相當於從./ZipCodeValidator中把ZipCodeValidator匯入,重新命名為RegExpBasedZipCodeValidator再匯出。記住用法即可。

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

2。整合匯出。*相當於全部匯出。下面程式碼整合了3個檔案,並全部匯出。

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

匯入關鍵字是import。

import基本用法

import { ZipCodeValidator } from "./ZipCodeValidator";

let myValidator = new ZipCodeValidator();

這裡./ZipCodeValidator匯出的東西里面有個名字叫ZipCodeValidator的,用它的名字匯入。這裡{xxx}={123},就是這種形式,其實用到了es6的解構賦值。不懂的轉戰es6。呼叫用其名字直接呼叫就可以了。

1。匯入重新命名 

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

 as語法,重新命名為ZCV

2。匯入全部

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

這裡匯入了./ZipCodeValidator檔案的所有匯出,而不是上個例子僅匯入ZipCodeValidator。as validator相當於給了所有匯出一個名字,可以理解為所有匯出的名稱空間。

Tips:

儘管不推薦這麼做,一些模組會設定一些全域性狀態供其它模組使用。 這些模組可能沒有任何的匯出或使用者根本就不關注它的匯出。 使用下面的方法來匯入這類模組:

import "./my-module.js";

3。預設匯出。default

比如

declare let $: JQuery;
export default $;

引用

import $ from "JQuery";

$("button.continue").html( "Next Step..." );

default只能匯出一項,僅匯出一個函式,或者一個介面,或者任何一個單項的東西,default是最佳實踐。因為在import的時候不指定匯入的東西預設匯入default。看下面這個例子

export default class ZipCodeValidator {
    static numberRegexp = /^[0-9]+$/;
    isAcceptable(s: string) {
        return s.length === 5 && ZipCodeValidator.numberRegexp.test(s);
    }
}
import validator from "./ZipCodeValidator";

let validator = new validator();

 4。export = 和 import = require()

 TypeScript模組支援export =語法,以配合傳統的CommonJS和AMD的工作流。

export =語法定義一個模組的匯出物件。 它可以是類,介面,名稱空間,函式或列舉。

若要匯入一個使用了export =的模組時,必須使用TypeScript提供的特定語法import let = require("module")

複製程式碼

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

複製程式碼

複製程式碼

import zip = require("./ZipCodeValidator");

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

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

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

複製程式碼

5。編譯為其他模組標準。

SimpleModule.ts

import m = require("mod");
export let t = m.something + 1;

AMD / RequireJS SimpleModule.js

define(["require", "exports", "./mod"], function (require, exports, mod_1) {
    exports.t = mod_1.something + 1;
});

CommonJS / Node SimpleModule.js

let mod_1 = require("./mod");
exports.t = mod_1.something + 1;

UMD SimpleModule.js

複製程式碼

(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        let v = factory(require, exports); if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports", "./mod"], factory);
    }
})(function (require, exports) {
    let mod_1 = require("./mod");
    exports.t = mod_1.something + 1;
});

複製程式碼

System SimpleModule.js

複製程式碼

System.register(["./mod"], function(exports_1) {
    let mod_1;
    let t;
    return {
        setters:[
            function (mod_1_1) {
                mod_1 = mod_1_1;
            }],
  execute: function() {
            exports_1("t", t = mod_1.something + 1);
        }
    }
});

複製程式碼

Native ECMAScript 2015 modules SimpleModule.js

import { something } from "./mod";
export let t = something + 1;

命令列的命令就是加 --module或者-m加要編譯的規範名稱。比如編譯為amd

tsc text.ts -m amd

用tsc --help可以看到-m的各項引數。

高階載入

typescript的按需載入。也叫動態載入。看我部落格前幾篇的webpack中,用的require.ensure做按需載入,感覺比較麻煩。然而ts的按需載入的簡單得益於它的省略引用。即:

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

這種模式的核心是import id = require("...")語句可以讓我們訪問模組匯出的型別。 模組載入器會被動態呼叫(通過require

示例: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 { ZipCodeValidator as Zip } from "./ZipCodeValidator";

if (needZipValidation) {
    require(["./ZipCodeValidator"], (ZipCodeValidator: typeof Zip) => {
        let validator = new ZipCodeValidator();
        if (validator.isAcceptable("...")) { /* ... */ }
    });
}

複製程式碼

只要if條件不成立,模組是不載入的。出個簡單的例子:

text.ts

import m= require("revence");
export let t=1;;

revence.ts

export = {
    mod:1
}

編譯為amd模組:根本沒有引入剛才的模組。

define(["require", "exports"], function (require, exports) {
    exports.t = 1;
    ;
});

現在改為text.ts

import m= require("revence");
export let t=m.mod+1;;

編譯出來為

define(["require", "exports", "revence"], function (require, exports, m) {
    exports.t = m.mod + 1;
    ;
});

模組載入的最佳實踐

1。儘可能地在頂層匯出

使用者應該更容易地使用你模組匯出的內容。 巢狀層次過多會變得難以處理,因此仔細考慮一下如何組織你的程式碼。

2。模組裡避免使用名稱空間

模組中使用名稱空間是不必要的,在模組中匯出的東西肯定不能重名,而匯入時使用者肯定會為其命名或者直接使用,也不存在重名,使用名稱空間是多餘的。

3。如果僅匯出單個 class 或 function,使用 export default。

如剛才所說,default是比較好的實踐。

4。如果要匯出多個物件,把它們放在頂層裡匯出

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

5。匯入時明確地列出匯入的名字

import { SomeType, SomeFunc } from "./MyThings";
let x = new SomeType();
let y = someFunc();

6。匯入大量模組時使用名稱空間

注意是匯入時。

export class Dog { ... }
export class Cat { ... }
export class Tree { ... }
export class Flower { ... }
import * as myLargeModule from "./MyLargeModule.ts";
let x = new myLargeModule.Dog();

7。使用重新匯出進行擴充套件

你可能經常需要去擴充套件一個模組的功能。 JS裡常用的一個模式是JQuery那樣去擴充套件原物件。 如我們之前提到的,模組不會像全域性名稱空間物件那樣去合併。 推薦的方案是不要去改變原來的物件,而是匯出一個新的實體來提供新的功能。

危險訊號

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

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