# ES Module import/export 和 設計思維 > 前言: > 最近在工作中覺得 import/export 的方式很多變與彈性,所以想好好的來理解討論一下: 現代前端開發日趨複雜,但無論技術如何演進,有一個核心概念始終不變:**最終所有的程式碼都會轉錄成瀏覽器能理解的 HTML、CSS、JavaScript**。 現代前端開發的構築趨勢可以從三個面向來理解: 1. **模組化開發**:將程式碼拆分成可重用的模組 2. **打包工具協助編譯**:使用 Webpack、Vite 等工具處理複雜的依賴關係 3. **最終輸出標準格式**:轉換成瀏覽器原生支援的格式 ## HTML `<script>` 標籤:JavaScript 的載體 網頁中讓 JavaScript 執行的載體是透過 HTML `<script>` 標籤。這個標籤不只可以引用 JavaScript,也可以引用其他類型的腳本,但最常見的用途就是載入 JavaScript。 HTML 引入模組化 JavaScript 主要有兩種形式: ### 1. CommonJS 模組 ```html <script type='text/javascript' src='./bundle.js'></script> ``` ### 2. ES Module 模組 ```html <script type='module' src='./main.js'></script> ``` 在前端打包工具中,通常會這樣設置(以 ES Module 為例): ```javascript const config = { type: 'module' } ``` --- ## CommonJS:傳統的模組系統 CommonJS 是 ES6 之前主要的模組化解決方案,主要用於 Node.js 環境。 ### 匯出語法 ```javascript // utils.js module.exports.add = function(a, b) { return a + b; } module.exports.subtract = function(a, b) { return a - b; } ``` ### 引入語法 使用 `require()` 函數來引入模組: ```javascript // app.js const { add, subtract } = require('./utils') console.log(add(5, 5)) // 10 console.log(subtract(10, 5)) // 5 ``` ## ES Modules:現代標準 ES Modules(ESM)是 ECMAScript 6 引入的官方模組系統,也是現代前端開發的主流選擇。 ### 1. 命名匯出(Named Export) **匯出方式**: ```javascript // utils.js export function addition(a, b) { return a + b } export const subtraction = (a, b) => a - b function multiplication(a, b) { return a * b } const division = (a, b) => a / b // 批量匯出 export { division, multiplication } ``` **引入方式**: ```javascript // app.js import { addition, subtraction, multiplication as multiply, // 使用別名 division as div } from './utils.js' addition(5, 3) // 8 subtraction(5, 3) // 2 multiply(4, 2) // 8 div(6, 2) // 3 ``` **批量引入**: 當需要引入大量模組時,可以使用 `*` 語法: ```javascript import * as utils from "./utils.js" utils.addition(5, 3) // 8 utils.subtraction(5, 3) // 2 utils.multiplication(4, 2) // 8 utils.division(6, 2) // 3 ``` ### 2. 預設匯出(Default Export) 每個檔案只能有一個預設匯出,引入時可以自由命名: ```javascript // utils1.js function isOdd(num) { return num % 2 !== 0 } export default isOdd ``` ```javascript // utils2.js const isEven = (num) => num % 2 === 0 export default isEven ``` ```javascript // app.js import isOdd from './utils1' import shouldBeEven from './utils2' // 可以自定義名稱 console.log(isOdd(3)) // true console.log(shouldBeEven(3)) // false ``` ## 實戰案例:useReducer Counter 讓我們透過一個實際的例子來展示如何善用 `index.ts` 作為資料夾的統一入口點。 ### 專案結構 ``` . ├── Counter.tsx └── stores ├── actions.ts ├── index.ts ├── reducer.ts └── state.ts ``` ### 1. 建立核心檔案 **state.ts**: ```typescript export interface State { count: number } export const initialState: State = { count: 0 } ``` **actions.ts**: ```typescript= type ActionType = 'decrement' | 'increment' export interface Action { type: ActionType } export const decrement = (): Action => ({ type: 'decrement' }) export const increment = (): Action => ({ type: 'increment' }) ``` **reducer.ts**: ```typescript= import { type Action } from './actions' import { type State } from './state' export const reducer = (state: State, action: Action): State => { switch (action.type) { case 'decrement': return { ...state, count: state.count - 1 } case 'increment': return { ...state, count: state.count + 1 } default: return state } } ``` ### 2. 建立統一入口點 **index.ts**: ```typescript= export * from './actions' export * from './reducer' export * from './state' ``` ### 3. 在 Recat 元件中使用 ```typescript= import { useReducer } from 'react' // 透過單一入口點引入所有需要的內容 import { decrement, increment, initialState, reducer } from './stores' const Counter = () => { const [state, dispatch] = useReducer(reducer, initialState) return ( <div> <h1>Counter</h1> <p>Count: {state.count}</p> <button onClick={() => dispatch(increment())}>Increment</button> <button onClick={() => dispatch(decrement())}>Decrement</button> </div> ) } export default Counter ``` ## 設計模式:Facade Pattern(外觀模式) 透過 `index.ts` 的概念,我們實際上實踐了 **Facade Pattern(外觀模式)**。這個模式讓呼叫端可以透過一個簡潔的介面來使用子系統的功能,而不需要了解內部的複雜細節。 ### 優點 - **簡化引入**:只需要從一個地方引入所有相關功能 - **降低耦合**:外部組件不需要知道內部檔案結構 - **易於維護**:內部結構調整時,只需要修改 index 檔案 - **提升可讀性**:程式碼引入部分更加簡潔明瞭 ## 為什麼選擇 ES Modules? ### 1. 瀏覽器原生支援 這是最重要的原因。現代瀏覽器原生支援 ES Modules,不需要額外的 polyfill 或轉換工具。 ### 2. 性能優化(Tree Shaking) ES Modules 的**靜態結構**特性讓打包工具(如 Vite、Webpack)能夠在建構時精準地移除未使用的程式碼,大幅縮小最終檔案體積。這對於追求極致載入速度的前端應用來說是無法抗拒的優勢。 ### 3. 更好的工具支援 現代開發工具對 ES Modules 有更完善的支援,包括: - 更準確的靜態分析 - 更好的自動完成功能 - 更精確的重構工具 ## TypeScript 模組路徑別名設定 在現代前端專案中設定路徑別名(例如用 `@/` 代表 `src/`),遵循**兩步驟原則**: ### 第一步:設定 TypeScript(開發環境) 修改 `tsconfig.json`: ```json { "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["src/*"] } } } ``` **參數說明**: - `"baseUrl": "."` - 設定解析的基準目錄 - `"paths"` - 定義路徑別名的對應規則 ### 第二步:設定打包工具(建構環境) 在打包工具的設定檔中加入別名: ```javascript // 通用概念 resolve: { alias: { '@': 'src 資料夾的絕對路徑' } } ``` > **重要提醒**: > - 打包工具需要提供絕對路徑 > - 確保兩個步驟的對應關係一致 > - 不同打包工具有不同的設定語法 ## 總結 ES Modules 的設計哲學其實反映了現代前端開發的核心思維:**模組化、可維護性、和開發體驗的最佳化**。 ### 從技術角度看 ES Modules 不只是語法糖,它真正解決了前端開發中的幾個痛點: - **靜態分析**:讓工具能更好地理解我們的程式碼 - **Tree Shaking**:讓打包後的檔案更小更高效 - **開發體驗**:IDE 能提供更精準的提示和重構 ### 從設計角度看 透過實際案例,我們看到了如何運用 Facade Pattern 來組織程式碼。這不只是技術問題,更是設計思維的體現: - **抽象複雜度**:透過 index.ts 隱藏內部實作細節 - **單一職責**:每個模組專注於自己的功能 - **依賴反轉**:外部程式碼不依賴具體的檔案結構 ### 從實務角度看 **開發工具的設定需要考慮到整個工作流程**。不是只設定 TypeScript 就夠了,還要考慮打包工具、編輯器支援等等。