###### tags: `Rendering Patterns` # [Render] Module Pattern ## Intrioduction - 當程式庫隨著開發越來越龐大, 保持代碼的**maintainable**和**separated**變得越來越重要 - module pattern可以分割程式碼變得**smaller**或**reusable**的片段 - module 可將文件中的某些值保密(*private*) - module 裡面宣告的值, 默認 scope 就會在此 module 範圍裡, 要`export`特定的value,此 value 才能在 module 被使用 - 減少值在其他程式庫被使用時的命名衝突(name collisions), 因為此值不能用在全域(global scope) ## ES2015 Modules - 使用`export`keyword匯出 value, 要使用 module 的 value 則用`import`, 藉由將想保密的值放在 module 另一方面也可以降低污染全域變數的風險(overwrite values, naming collisions), 且每個 module 只可以有一個export default ```javascript= //math.js (modeule) const privateValue = "This is a value private to the module!"; export default function add(x, y) { return x + y; } export function multiply(x) { return x * 2; } export function subtract(x, y) { return x - y; } export function square(x) { return x * x; } ``` - **import module from 'module'**, default name 可自行命名 ```javascript= //index.js import otherName, { multiply, subtract, square } from "./math.js"; otherName(7, 8); //export dafault name is add multiply(8, 9); subtract(10, 3); square(3); ``` - **import { module } from 'module'** ```javascript= //index.js import { add, multiply, subtract, square } from "./math.js"; ``` - 當import name 與 local values相同時, 可用`as`重新命名import name ```javascript= //index.js(name collision) import { add, multiply, subtract, square } from "./math.js"; function add(...args) { return args.reduce((acc, cur) => cur + acc); } /* Error: add has already been declared */ function multiply(...args) { return args.reduce((acc, cur) => cur * acc); } /* Error: multiply has already been declared */ //index.js (rename by as keyword) import { add as addValues, multiply as multiplyValues, subtract, square } from "./math.js"; function add(...args) { return args.reduce((acc, cur) => cur + acc); } function multiply(...args) { return args.reduce((acc, cur) => cur * acc); } /* From math.js module */ addValues(7, 8); multiplyValues(8, 9); subtract(10, 3); square(3); /* From index.js file */ add(8, 9, 2, 10); multiply(8, 9, 2, 10); ``` - 用asterisk `*`可以import所有export, 存成物件後調用, 要注意是有可能導入不必要的value, 但不會導入private value除非你有export此value ```javascript= // The imported values are properties on the math object import * as math from "./math.js"; math.default(7, 8); math.multiply(8, 9); math.subtract(10, 3); math.square(3); ``` ## React - 用react開發應用程式通常會需要處理大量的component, 我們會將各個component寫在他自己的file內, 為每個component創建一個module [demo todo-list](https://codesandbox.io/embed/heuristic-brattain-ipcyb) - 注意:Button.js 和 Input.js 都有一個 style 物件, 由於 style 物件有他自己的 *module-scoped*, 因此在個別檔案中可以重複使用這個名稱不會有命名衝突(name collision) ### Static import - 缺點: - page loadind time較長 - 可能使用者還沒需要 module 時就先匯入 - 無法根據用戶輸入 or 外部接收數據...較彈性方式導入 module ```javascript= //Static import import './modules/module-name'; ``` ### Dynamic import( ES2020 (ES11) 提供的 `import()`)讓module匯入更彈性 - 優點: - 參數可以為表達式(expression), 像是template literals - 減少page loadind time - 只有在使用者真的需要用到時再load(images or data), parse(Converting an HTML source into DOM nodes, and generating an AST), and compile(Converting JavaScript into native machine code),避免第三方 module 還不需使用就先 import - 不依賴於硬編碼的模塊路徑,可根據用戶輸入、從外部源接收的數據、函數的結果等導入模塊的方式增加了靈活性 - 在 script 或 module 使用 import() 可以非同步的 import 模組。因為 import() 會回傳 Promise,所以常見有兩種寫法: - `Promise.prototype.then()` - `async / await` ```javascript= import("module").then(module => { module.default(); module.namedExport(); }); // Or with async/await (async () => { const module = await import("module"); module.default(); module.namedExport(); })(); ``` - 讓`import module`更彈性, 當需要時動態匯入 module, 比方說 `click` 再匯入 [demo](https://codesandbox.io/embed/green-sound-j60fl) ```javascript= //Dynamic import with template literals import(`./modules/${moduleName}.js`); const res = await import(`../assets/dog${num}.png`); //async import images ``` ```javascript= //index.js const button = document.getElementById("btn"); button.addEventListener("click", () => { import("./math.js").then((module) => { console.log("Add: ", module.add(1, 2)); console.log("Multiply: ", module.multiply(3, 2)); const button = document.getElementById("btn"); button.innerHTML = "Check the console"; }); }); ``` ## Reference [modules-dynamic-imports](https://javascript.info/modules-dynamic-imports)