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 class或export function(考慮使用export default) - 多個文件的頂層具有同樣的
export namespace Foo {(不要以為這些會合並到一個Foo中!)
