TypeScript模組系統、命名空間、聲明合併

  • 2019 年 10 月 3 日
  • 筆記

命名空間

命名空間能有效避免全局污染。在ES6引入模組之後,命名空間就較少被提及了。如果使用了全局的類庫,命名空間仍是一個好的解決方案。

namespace Shape{     const pi = Math.PI;     // 使用export關鍵字導出,可以在全局空間內可見     export function circle(r: number) {        return pi * r ** 2     }     square(5)  }  Shape.circle(10); // 可以在全局空間訪問導出的  import circle = Shape.circle; // 為命名空間內的變數起個別名,要清楚此處import與模組化的import含義不一樣  circle(20);

隨著程式的擴張,命名空間也會很大,需要對其進行拆分,在不同的文件中使用同名命名空間,他們之間共享命名空間。

// space1.ts  /// <reference path="space2.ts" />  // 三斜線引用標籤告訴編譯器,兩個文件中的命名空間內部存在著依賴關係  namespace Shape{     export function square(x: number) {        return x*x;     }     circle(10); // 只有circle被export後,這裡才能訪問  }    // space2.ts  /// <reference path="space1.ts" />  namespace Shape{     const pi = Math.PI;     // 使用export關鍵字導出,可以在全局空間內可見     export function circle(r: number) {        return pi * r ** 2     }     square(5);  }

命名空間最好不要和模組一起混用

模組化系統

TypeScript對ES6和CommonJS兩種模組系統都有很好的支援,我們基本可以沿用以前的寫法。但兩者不要混用,如果出現混用,就要使用TS準備的兼容性寫法。

先來看看ES6和CommonJS各自的寫法

// ES6導入  import { a, b } from './Modular System/es6/a';  import { a as f } from './Modular System/es6/a';  import * as All from './Modular System/es6/a';  import abc from './Modular System/es6/b';  import Obj from './Modular System/es6/a'  // ES6導出  export defalut Obj;  export {a,b,c};  export {d as D};  export {D as C}  from './a'; // 將a.ts中的D重新命名並導出,這種用法只能對a.ts中的非默認導出有效    // CommonJS導入  let c1 = require('./Modular System/node/a.ts');  let c2 = require('./Modular System/node/b');  // CommonJS導出  module.exports = a;  // 將a變數導出  exports.c = 3;  exports.d = 4;  相當於  module.exports = {c:3, d:4}  如果兩種方式並存,module.exports將會覆蓋exports.c這種方式的導出

兩種模組在導入導出時互不兼容:

  • 導出:ES6允許同時存在export default和export多個變數,而CommonJS只允許有一種形式的導出,其中一種會把另外一種覆蓋掉。
  • 導入:ES6可以按需導入也可以全部導入,而CommonJS只能全部導入。

如果在ES6模組中拋出數據,在非ES6模組中導入,就會出現問題。因此盡量不要混用不同的模組化系統。如果迫不得已,可以使用TS提供的兼容性語法:

// 導出  export = a;  // 導入  import c4 = require('../es6/c');  /*  1.如果使用以上方法導出,此文件不允許有其它形式的導出  2.以上形式的導出的數據,不僅可以用以上語法導入,還可以用es6的方式導入。前提是tsconfig.json中的"esModuleInterop":true配置項要開啟。  */

聲明合併

 編譯器會把程式的多個地方具有相同名稱的聲明合併成一個,這樣可以將程式散落在各處的重名聲明合併在一起。

例如:

interface StateMerge {     x: number,     y: string,  }  interface StateMerge {     y: string;     foo(bar: string[]): string[],  }  // 此時會將兩個聲明的同名介面成員合併  let stateMerge: StateMerge = {     x: 1,     y: "15",     foo(bar: any) {        return bar     }  };

如果合併的兩個結構內成員重名怎麼辦?

  • 對於非函數成員,必須類型一致,否則報錯。
  • 對於函數成員,會發生重載,重載的順序按照以下規則。
interface StateMerge {     x: number,     y: string,     foo(bar: number): number; // 4     foo(bar: string): string; // 5     foo(bar: "b"): number;    // 2  }  interface StateMerge {     y: string;     foo(bar: string[]): string[], // 3     foo(bar: "a"): number,  // 1  }

介面內部按照先後順序。介面之間,聲明在後的介面函數成員排序更靠前。

如果出現自變數,排名最靠前。後面的介面中的排在第一位,前面的介面排在第二位。上述排序如注釋所示。

函數和命名空間的合併

function Lib() {  }  namespace Lib{     export let version = '1.0'  }  console.log(Lib.version); // 相當於為函數Lib添加了屬性

類和命名空間的合併

class C{  }  namespace C{     export let state = 1  }  console.log(C.state); // 相當於為類C添加了state屬性

此外,還可以為枚舉增加屬性。

注意:在命名空間與類、函數進行生命合併的時候,一定要將命名空間放在類、函數之後。否則報錯。