`#React` `#六角前端課程` `#學習筆記` `#骨力走傱` ## 什麼是關注點分離? ### 🔸 介紹 關注點分離(Separation of Concerns)是指程式開發的一種設計原則。當資料越龐大、功能越複雜時,就需要將資料、功能拆分清楚,使程式碼更容易管理與維護。 #### 未採用關注點分離 資料與畫面綁在一起,程式碼不易維護、無法重複使用。 :::spoiler 程式碼 ```html= <ul class="ticketCard-area"> <li class="ticketCard"> <div class="ticketCard-img"> <a href="#"> <img src="https://github.com/hexschool/2022-web-layout-training/blob/main/js_week5/travel_1.png?raw=true" alt=""> </a> <div class="ticketCard-region">高雄</div> <div class="ticketCard-rank">10</div> </div> <div class="ticketCard-content"> <div> <h3> <a href="#" class="ticketCard-name">綠島自由行套裝行程</a> </h3> <p class="ticketCard-description"> 嚴選超高CP值綠島自由行套裝行程,多種綠島套裝組合。 </p> </div> <div class="ticketCard-info"> <p class="ticketCard-num"> <span><i class="fas fa-exclamation-circle"></i></span> 剩下最後 <span id="ticketCard-num"> 87 </span> 組 </p> <p class="ticketCard-price"> TWD <span id="ticketCard-price">$1400</span> </p> </div> </div> </li> <li class="ticketCard"> <div class="ticketCard-img"> <a href="#"> <img src="https://github.com/hexschool/2022-web-layout-training/blob/main/js_week5/travel_4.png?raw=true" alt=""> </a> <div class="ticketCard-region">台北</div> <div class="ticketCard-rank">2</div> </div> <div class="ticketCard-content"> <div> <h3> <a href="#" class="ticketCard-name">清境高空觀景步道</a> </h3> <p class="ticketCard-description"> 清境農場青青草原數十公頃碧草,這些景觀豐沛了清境觀景步道的風格,也涵養它無可取代的特色。 </p> </div> <div class="ticketCard-info"> <div class="ticketCard-num"> <p> <span><i class="fas fa-exclamation-circle"></i></span> 剩下最後 <span id="ticketCard-num"> 99 </span> 組 </p> </div> <p class="ticketCard-price"> TWD <span id="ticketCard-price">$240</span> </p> </div> </div> </li> </ul> ``` ::: #### 採用關注點分離 資料與畫面分離,程式碼易維護、可重複使用。 :::spoiler 程式碼 ```javascript= // 資料 let data = [ { id: 0, name: "肥宅心碎賞櫻3日", imgUrl: "https://images.unsplash.com/photo-1522383225653-ed111181a951?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1655&q=80", area: "高雄", description: "賞櫻花最佳去處。肥宅不得不去的超讚景點!", group: 87, price: 1400, rate: 10 }, { id: 1, name: "貓空纜車雙程票", imgUrl: "https://images.unsplash.com/photo-1501393152198-34b240415948?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1650&q=80", area: "台北", description: "乘坐以透明強化玻璃為地板的「貓纜之眼」水晶車廂,享受騰雲駕霧遨遊天際之感", group: 99, price: 240, rate: 2 } ]; ``` ```javascript= // 資料處理與渲染邏輯 function renderTicker(tickets) { let ticketList = ""; tickets.forEach(function (ticket) { ticketList += `<li class="ticketCard"> <div class="ticketCard-img"> <a href="#"> <img src="${ticket.imgUrl}" alt=""> </a> <div class="ticketCard-region">${ticket.area}</div> <div class="ticketCard-rank">${ticket.rate}</div> </div> <div class="ticketCard-content"> <div> <h3> <a href="#" class="ticketCard-name">${ticket.name}</a> </h3> <p class="ticketCard-description"> ${ticket.description} </p> </div> <div class="ticketCard-info"> <p class="ticketCard-num"> <span><i class="fas fa-exclamation-circle"></i></span> 剩下最後 <span id="ticketCard-num"> ${ticket.group} </span> 組 </p> <p class="ticketCard-price"> TWD <span id="ticketCard-price">$${ticket.price}</span> </p> </div> </div> </li>`; }); // 渲染 ticketCardArea.innerHTML = ticketList; // 渲染文字 searchResultText.textContent = `本次搜尋共 ${tickets.length} 筆資料`; }; renderTicker(data); ``` ::: ### 🔸 核心概念 ![VS---17’23”](https://hackmd.io/_uploads/r1fC1n6Z-g.jpg) 1. 定義資料。 2. 資料處理與渲染畫面分開執行。 3. 著重在資料處理的能力。 #### 資料來源 * [關注點分離](https://ithelp.ithome.com.tw/articles/10238118) * [關注點分離 Separation of Concerns (SOC)](https://hackmd.io/@Codeitaday/r1tyE6Luo) ## Vite 安裝與環境建置環境 ### 🔸 為什麼要使用 Vite? > Vite 讓前端開發從「繁瑣設定」變成「直接開發」。 #### 1. 快速建立開發環境 * 不需從零設定環境。 * 透過預設選項即可立即開始開發。 #### 2. 適合開發單頁式應用(SPA) * 快速建立前端應用服務。 * 支援現代前端框架(如 React、Vue)。 #### 3. 整合性高的開發工具 * 將多種開發流程整合在同一工具中。 * 降低環境設定與維護成本。 #### 4. 套件管理一致化 * 由 CDN 改為 NPM 管理套件。 * 依賴關係與版本更可控。 ### 🔸 環境建立 ![螢幕擷取畫面 (1708)](https://hackmd.io/_uploads/HJ5GOFkHWl.png) ``` npm create vite@latest ``` * Project name: (自定義專案名稱) * Select a framework: › React * Select a variant: › JavaScript ### 🔸 安裝 NPM ``` npm install ``` ### 🔸 運行方式 ``` npm run dev ``` * 運行開發環境。 * `dev` 並不是 npm 的指令,是透過 package.json 檔案新增指令的。 ### 🔸 專案環境結構說明 #### 1. `node_modules` * 存放專案所使用的第三方套件。 * 由套件管理工具自動產生,不需手動修改。 #### 2. `public` * 不會被編譯。 * 存放靜態資源(如圖片、favicon)。 * 內容會直接以原始檔案形式提供給瀏覽器。 #### 3. `src` * 會被編譯。 * 主要開發目錄,所有應用程式邏輯都在此。 * `main.jsx` 為專案進入點(entry point)。 * 所有元件與程式碼必須從此檔案被引入,才會納入編譯流程。 #### 4. `eslint.config` * 程式碼品質與風格檢查工具設定檔。 * 用於檢查錯誤、潛在問題與一致的撰寫規範。 #### 5. `.gitignore` * 設定 Git 要忽略的檔案與資料夾。 * 通常包含不需版控的檔案(如 `node_modules`、環境設定檔)。 #### 6. `package.json`、`package-lock.json` * 專案與套件的描述檔。 * 記錄: * 安裝的套件。 * 套件版本。 * 專案指令(scripts)。 * `package-lock.json` 用於鎖定實際安裝的套件版本。 #### 7. `vite.config` * Vite 的設定檔。 * 用來設定: * 專案啟動方式。 * 編譯與打包行為。 * 編譯後輸出的調整方式。 ### 🔸 專案部署流程(GitHub Pages) #### 1. 將專案放上 GitHub * 建立 GitHub Repository。 * 專案需先完成 `commit` 並 `push`。 #### 2. 透過 Git 指令操作部署流程 * 本地端以 git 管理版本與上傳程式碼。 #### 3. 建立正式版檔案 ``` npm run build ``` * 產生可部署的靜態檔案。 * Vite 預設輸出至 `dist` 資料夾。 #### 4. 安裝 `gh-pages` 套件 ``` npm install gh-pages --save-dev ``` * 用途:將靜態檔案一鍵部署至 GitHub Pages。 * 屬於開發工具,因此通常放在 `devDependencies`。 #### 5. 設定部署指令 在 `package.json` 的 `"scripts"` 中加入: ``` "deploy": "gh-pages -d dist" ``` * `gh-pages`:執行部署套件。 * `-d dist`:指定要部署的資料夾。 * `-d` = **directory(目錄)**。 #### 6. 執行部署 ``` npm run deploy ``` * 若成功,終端機會顯示 `Published`。 * 代表已成功部署到 GitHub Pages。 #### 核心重點總結 * `build`:產生靜態檔案。 * `gh-pages`:負責把 `dist` 推到 GitHub Pages。 * `deploy`:實際執行部署的指令。 ### 🔸 調整靜態檔案路徑(Vite) > GitHub Pages 不是從根目錄部署時,必須設定 `base`,否則靜態資源會找不到。 #### 問題與說明 * Vite 預設使用**絕對路徑**載入靜態資源。 * 部署到 **GitHub Pages(非根目錄)** 時,資源路徑會錯誤。 * 必須調整為對應 Repository 的路徑。 #### `vite.config.js` 設定 ```javascript export default defineConfig({ base: process.env.NODE_ENV === "production" ? "/Repository名稱/" : "/", plugins: [react()], }) ``` #### 1. `base` * 設定專案的基礎路徑(所有靜態資源的前綴)。 #### 2. 環境判斷 ```javascript process.env.NODE_ENV === "production" ``` * 判斷目前是否為正式環境: | 指令 | 環境 | 路徑 | | --------------- | ---- | ---------------- | | `npm run dev` | 開發環境 | `/` | | `npm run build` | 正式環境 | `/Repository名稱/` | #### 為什麼這樣做? * 本地開發仍維持 `/`,避免路徑錯誤。 * 部署後資源能正確從 GitHub Pages 的子路徑載入。 ### 🔸 載入外部套件 #### 1️⃣ Sass **安裝** ```bash npm add -D sass ``` **引入** ```scss @import "./assets/all.scss"; ``` **補充** * 若使用 **Vue**,建議安裝: ```bash npm add -D sass-embedded ``` * 通常在 `main.jsx`(或 main.js)中引入全域樣式。 #### 2️⃣ Bootstrap **安裝** ```bash npm i bootstrap ``` **引入(使用 SCSS)** ```scss @import "bootstrap/scss/bootstrap.scss"; ``` **補充** * 可指定版本: ```bash npm i bootstrap@5.3.8 ``` * 未指定版本則安裝最新穩定版。 #### 3️⃣ Bootstrap 客製化變數(SCSS) **目的** * 覆寫 Bootstrap 預設樣式(顏色、字體、間距等) **複製變數檔** * 路徑: ``` node_modules/bootstrap/scss/ ``` * 複製以下檔案: * `_variables.scss` * `_variables-dark.scss` * 貼到: ``` src/assets/ ``` **自定義變數** * 在 SCSS 定義變數,再引入 Bootstrap: ```scss $theme-colors: ( "primary-100": #FBF7F5, "primary-300": #FEA375, "primary-500": #F2783C, "primary-700": #C26030, "primary-900": #613018, "primary-950": #30180C, "secondary-500": #433D39, "secondary-700": #252322, "secondary-900": #000000 ); ``` **原因:** 使用覆蓋的方式,覆蓋 Bootstrap 的變數,以達修改目的,故 Bootstrap 必須最後引入。 ```scss @import "bootstrap/scss/bootstrap"; ``` #### 4️⃣ axios **安裝** ```bash npm install axios ``` **在 App.jsx 引入** ```jsx // 外部套件(第三方)放前面 import { useEffect, useState } from 'react' import axios from 'axios' // 內部資源放後面 import reactLogo from './assets/react.svg' import viteLogo from './assets/vite.svg' ``` **慣例原則:** * **外部套件 → 內部模組 → 靜態資源** * 有助於可讀性與維護性。 #### 總結重點 * Sass:處理樣式與變數管理。 * Bootstrap:快速建立 UI。 * Bootstrap SCSS:可高度客製化設計系統。 * axios:負責 API 請求。 * 引入順序與結構,是專案可維護性的關鍵。 ### 🔸 環境變數 > **環境變數的目的,是讓「設定跟著環境走,程式碼不需要動」。** #### 1️⃣ 為什麼需要環境變數? 在實務開發中,同一份程式碼會跑在不同環境: * **本地開發**:`localhost` * **內部測試**:`dev.xxx` * **正式上線**:`www.xxx` 如果每個環境都手動修改 API 路徑或設定: * 容易出錯。 * 維護成本高。 * 不利於部署流程。 解法: **將會變動的設定抽離成環境變數,由環境決定實際值。** #### 2️⃣ Vite 的 `.env` 檔案規則 **官方文件:** > [https://vite.dev/guide/env-and-mode#env-files](https://vite.dev/guide/env-and-mode#env-files) Vite 會依照檔名與模式自動載入環境變數: ```bash .env # 所有模式都會載入 .env.local # 所有模式都會載入(Git 忽略) .env.[mode] # 僅指定模式載入(如 .env.development) .env.[mode].local # 指定模式載入(Git 忽略) ``` **常見模式:** * `development` * `production` #### 3️⃣ 環境變數命名規則(Vite) * **必須加上 `VITE_` 前綴** * 否則前端程式無法讀取 **範例(`.env`):** ```env VITE_APP_PATH=https://randomuser.me/api/ ``` #### 4️⃣ 在程式中使用環境變數 **基本引入方法** ```js import.meta.env.VITE_APP_PATH ``` **解構寫法(推薦)** ```js const { VITE_APP_PATH } = import.meta.env ``` **實務應用** ```js axios.get(VITE_APP_PATH) ``` #### 5️⃣ 使用流程總結 1. 建立 `.env`(或 `.env.development`)。 2. 設定 `VITE_` 開頭的變數。 3. 透過 `import.meta.env` 讀取。 4. 取代寫死的路徑或設定值。 5. 切換環境時無需改程式碼。 ### 🔸 資料來源 * [Day16 - Node.js 是什麼?為什麼大家都在使用?](https://ithelp.ithome.com.tw/articles/10331821) * [Vite 是什麼? 為什麼要用 Vite? 它解決了哪些問題? 又是如何解決?](https://www.explainthis.io/zh-hant/swe/what-is-vite) * [React 開發者前端建構工具之路](https://ithelp.ithome.com.tw/articles/10321762) ## 認識 React ### 🔸 學習流程 ![VS---2’52”](https://hackmd.io/_uploads/r1NzVenG-e.jpg) ### 🔸 建構方法 * **HTML** → CDN(少見) * **Vite** → 前端常用的方法、SPA(常見) * **Next.js** → SSR 前端整合形式 ### 🔸 五步驟載入 React :::warning 以下是課程教學使用,實務上建議使用 Vite 或其他建構工具載入 React。 ::: #### 1. 載入資源 **[React](https://legacy.reactjs.org/docs/cdn-links.html#gatsby-focus-wrapper)** ```html= // React 函式庫 <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script> // React DOM 操作 <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script> ``` * `development`:開發模式。 * `production`:正式機。 **[JSX](https://zh-hant.legacy.reactjs.org/docs/add-react-to-a-website.html#quickly-try-jsx)** ```html= <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> ``` * JSX 是一種 JavaScript 語法擴充,可以在 JavaScript 裡直接寫「像 HTML 的結構」,以提升 React 程式碼的可讀性。 * Babel 是編譯器,負責把 JSX 轉成瀏覽器看得懂的 JavaScript。 * 若要使用 Babel,需要在 `<script>` 中加入 `type="text/babel"`,才能正確運行。 #### 2. 測試是否有正確引入 ```jsx= console.log(React, ReactDOM); ``` * 會回傳兩筆物件,一個是 React 函式庫,一個是 ReactDOM 物件。 * ReactDOM 的 DOM 必須是大寫。 #### 3. 加入 root 元素 ```html= <div id="root"></div> ``` * React 的元素都會生成在 root 裡。 #### 4. 建立 React 元件 ```jsx= function App() { return <h1>React 我來了</h1> } ``` * React 的元件都是函式。 * 函式名稱必須大寫。 * 因為 JSX 的關係,不必加入反引號,就可以寫出「像 HTML 的結構」。 #### 5. 渲染元件至 root ```jsx= const el = document.querySelector("#root"); const root = ReactDOM.createRoot(el); root.render(<App />); ``` * `ReactDOM.createRoot()`:建立一個根元素的節點。 * `root.render(<App />)`: * 在根元素的節點中,渲染 App 元件產生的畫面。 * 必須加上結尾標籤 `/`。 ### 🔸 資料驅動 React:建立版型 ```jsx= function App() { return <div className="card"> <img src="https://images.unsplash.com/photo-1765600112366-6688f98dfa33?q=80&w=687&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" className="card-img-top" alt="..." /> <div className="card-body"> <h5 className="card-title">Card title</h5> <p className="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p> <a href="#" className="btn btn-primary">Go somewhere</a> </div> </div> } const el = document.getElementById('root'); const root = ReactDOM.createRoot(el); root.render(<App />); ``` * 基於 React 的規則: * `<img>` 標籤必須加上結尾標籤 `< />`。 * 屬性名稱 `class` 必須修改成 `className`。 ### 🔸 資料驅動 React:資料抽離 ```jsx= // 資料抽離 // 觀察上一段程式碼 有哪些內容可以作為資料抽離 function App() { const data = { imageUrl: "https://images.unsplash.com/photo-1765600112366-6688f98dfa33?q=80&w=687&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", title: "Card title", content: "Lorem ipsum dolor sit amet consectetur.", link: "https://www.google.com/?hl=zh_TW" } return <div className="card"> <img src={ data.imageUrl } className="card-img-top" alt="..." /> <div className="card-body"> <h5 className="card-title">{ data.title }</h5> <p className="card-text">{ data.content }</p> <a href={ data.link } className="btn btn-primary">Go somewhere</a> </div> </div> } const el = document.getElementById('root'); const root = ReactDOM.createRoot(el); root.render(<App />); ``` * 使用 `{}` 帶入物件中的資料。 * 其中有一項規則是只能放入表達式。 :::warning `return` 之前的程式碼是為了決定資料狀態,而之後則是如何將資料渲染至畫面。 ::: ### 🔸 什麼是 JavaScript 陳述式?表達式? > [JavaScript 表達式觀念及運用](https://www.casper.tw/development/2020/09/17/js-expression/) > 在 JavaScript 中,語句分為陳述式與表達式,而在 React 中可內嵌表達式,因此使用 React 時,需要認識兩種語句的差異。 除此之外,學會區分陳述式與表達式,就知道「什麼是流程、什麼是結果」,是使得程式碼更加精簡的關鍵。 #### 陳述式的特徵 * 由多個單字或句子組成。 * 目的是執行某些操作,不會回傳值。 * `if...else`、`try...catch`、`var`、`let`、`const` ...... * 控制流程。 #### 表達式的特徵 * 1 ~ 5 個左右的單字。 * 可以賦值給變數,會回傳值。 * `10`、`1+1`、`'hello'`、`myFunction()` ...... * 回傳結果。 #### 比較 * `let a = 1;`(不會回傳值) * `a = 2;`(會回傳 2) #### 陳述式可以包表達式 ```javascript= if(true) { console.log(123); }; ``` * 雖然執行後會回傳 123,但這是因為 `console.log(123);` 的緣故,並非 `if` 的關係。 * `if` 為陳述式,`console.log` 為表達式。 ### 🔸 表達式與 React 的關係 #### 使用三元運算子替換元件資料 ```jsx= function App() { const data = { name: "卡斯伯" } return <div className="card w-50"> <img src={data.imageUrl} className="card-img-top" alt="..." /> <div className="card-body"> <h5 className="card-title">{data.name}</h5> {data.name ? data.name : "小花"}, {/* 使用三元運算子替換使用者名稱或其他資料 */} </div> </div> } ``` * 若 `data.name` 無資料會回傳「小花」,若有,則是回傳 `data.name` 的值。 #### 可帶入函式表達式 ```javascript= // 宣告式 function fn() {} // 表達式 --> 可放 JSX 裡的 const fn = function () {} ``` ```jxs= {(function () { return 2 * 2 })()} ``` * 執行函式方式有兩種: * `fn();` * `(function (){})();` ### 🔸 JS 型別與 JSX #### JSX 不會呈現物件型別及特定的原始型別 * 在 `{}` 中帶入物件會報錯,例如 `{ data }`,會回傳「這不是有效的 React 子元素。」的錯誤訊息。 * `{ true }`、`{ false }`、`{ undefined }`、`{ null }` 不會呈現在畫面上,若要呈現,可使用三元運算子呈現。 * `{ true ? "true" : "false" }` #### 陣列中的值會直接呈現在畫面上 * `{ [1, 2, 3, <div>我是小花</div>] }` * 可以在陣列中加入 html 的結構,會作為標籤結構呈現在畫面上,但需要加入 key 值(後續會提到)。 #### 將 HTML 結構賦值在變數 ```jsx= const list = [<li class="list-group-item">An item</li>,<li class="list-group-item">A second item</li>,<li class="list-group-item">A third item</li>]; <ul className="list-group list-group-flush"> { list } </ul> ``` * 綜合上述的特性,可以將 HTML 結構賦值在變數上,在元件中只要帶入該變數,便能帶入整組 html 結構。 ### 🔸 JSX 是如何運作的 #### 簡介 | # | 功能 | | ----- | ------------------------------------------------------------------------------------------------ | | JSX | JavaScript 語法擴充,可以在 JavaScript 裡直接寫「像 HTML 的結構」,以提升 React 程式碼的可讀性。 | | Babel | 編譯器,負責把 JSX 轉成瀏覽器看得懂的 JavaScript。 | #### JS 原生寫法 ```javascript= import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; function App() { return _jsxs("h1", { children: ["React \u6211\u4F86\u4E86 ", _jsx("small", { className: "text-danger", children: new Date().toLocaleDateString() })] }); } const el = document.getElementById('root'); const root = ReactDOM.createRoot(el); root.render(_jsx(App, {})); ``` #### JSX 寫法 ```jsx= function App() { return <h1>React 我來了 <small className="text-danger">{new Date().toLocaleDateString() }</small></h1> } const el = document.getElementById('root'); const root = ReactDOM.createRoot(el); root.render(<App/>) ``` ### 🔸 JSX 與 HTML 的標籤屬性 > 基於 React 的規則,需要將 HTML 標籤做一些修改,才能在 React 的環境中運行。 #### className ```html= <div> <button type="button" className="btn btn-primary">Primary</button> </div> ``` * 屬性 `class` 需改為 `className`。 #### checked 與結尾標籤 ```html= <div> <input type="text" defaultValue="小花" /> </div> ``` * 若要設定預設值,要將屬性 `value` 需改為 `defaultValue`。 * `input` 需要加上結尾標籤,或是以 `/` 結尾即可。 #### htmlFor ```html= <div> <label htmlFor="email">請輸入 Email</label> <input type="email" id="email" /> </div> ``` * 屬性 `for` 需改為 `htmlFor`。 #### selected ```html= <div> <select name="" id="" defaultValue="2"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> </div> ``` 在 HTML 中,若要指定預設值為 2 的話,語法會是 `<option value="2" selected="">2</option>`;而在 React 裡,則是寫在標籤 `select`,使用 `defaultValue` 設定預設值。 #### textarea ```html= <div> <textarea name="" id="" cols="30" rows="5" defaultValue="這裡可以放多行文字" /> </div> ``` * 在標籤 `textarea` 加入屬性 `defaultValue` 設定預設值。 * 需要加上結尾標籤,或以 `/` 結尾。 #### dangerouslySetInnerHTML ```jsx= function App() { const htmlTemplate = { __html: '<div>這裡有一段文字</div>' }; return ( <div dangerouslySetInnerHTML={ htmlTemplate }></div> ) } ``` * 基於安全問題,無法直接使用 `{htmlTemplate}` 渲染 HTML 結構,這麼做會以純字串呈現在畫面上。 * 若要渲染資料的話: 1. 需要使用屬性 `dangerouslySetInnerHTML`。 2. 將資料改為物件,並加上屬性名稱 `__html`。 #### onChange ```jsx= <div> <input type="text" onChange={ function(e){ console.log(e.target.value); } } /> </div> ``` * `onChange`:綁監聽 + 監聽事件為 `Change`。 * 若要取得表格內的值,可在標籤 `input` 加入屬性 `onChange`,接著使用 `function` 取得表格內的值。 ### 🔸 React 行內樣式 #### 縮寫渲染程式碼 ```jsx= // 縮寫前 // const el = document.querySelector("#root"); // const root = ReactDOM.createRoot(el); // root.render(<App />) // 縮寫後 // 1. ReactDOM.createRoot(document.querySelector("#root")).render(<App />); // 2. ReactDOM .createRoot(document.querySelector("#root")) .render(<App />); ``` 1. `document.querySelector("#root")` 移到 `ReactDOM.createRoot` 裡。 2. `ReactDOM.createRoot(document.querySelector("#root"));` 後方直接接 `render()`。 #### 屬性 style ```jsx= return ( <div className='card' style={{width: "18rem"}}> <div className='card-img-top' style={{ height: "200px", backgroundImage: `url('https://images.unsplash.com/photo-1564564321837-a57b7070ac4f?ixlib=rb-1.2.1&amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;auto=format&amp;fit=crop&amp;w=2352&amp;q=80')`, backgroundSize: "cover", backgroundPosition: "center center" }}></div> </div> ); ``` * 使用屬性 `style` 加入行內樣式,`style={{}}` 的第一個括號表示可帶入表達式,第二個括號表示物件,因此需要此物件的格式帶入資料。 * 屬性名稱不可以有 `-`,因此使用小駝峰命名法。 * 遇到網址這類帶有特殊符號的值,使用反引號帶入資料。 ### 🔸 JSX 開發中常見的錯誤 * 使用大寫定義元件,例如 `App()`。 * 多個元素外層需要使用標籤包覆,若希望不要產生多餘的標籤例如 `<div>`,可以使用 `React.Fragment` 或 `<></>` 包覆程式碼。 * 沒有結尾標籤。 * 未使用正確的屬性名稱,或引入方式,例如 `className`、`style={{}}`。 * JSX 的本質是 JavaScript,會避免屬性名稱與 JavaScript 的關鍵字相同,例如 `class`、`for` ... 不建議使用。 * `return` 後方若無程式碼,會因**自動插入分號**的機制中斷執行;若需要繼續執行,可加入 `()`。 ```jsx= // 會執行 <div> function App() { return <div> 123 </div>; }; // 不會執行 <div> function App() { return // 若後方無程式碼會中斷執行 <div> 123 </div>; }; // 加入 () 繼續執行 function App() { return ( // 需要與 return 同一行 <div> 123 </div>); }; ``` ## JavaScript 必學觀念 ### 🔸 常見縮寫 #### 語法糖與新增語法 ```javascript= // 語法糖 const obj = { myName: '物件', fn: function() { return this.myName; } }; console.log(obj.fn()); // 物件 // 箭頭函式 const obj = { myName: '物件', fn: () => { return this.myName; } } console.log(obj.fn()); // error 結果與上方不同 不是語法糖 ``` * 語法糖的邏輯與當前的 JS 一致,只是提供更簡潔的寫法。 * 若會改變邏輯的話,表示當前的寫法是新增語法,而非語法糖,例如箭頭函式。 ### 🔸 物件字面值 #### 物件內的函式 ```javascript= // 縮寫前 const obj3 = { myName: '物件', fn: function() { // 縮寫物件內的函式 return this.myName; } }; // 縮寫後 const obj3 = { myName: '物件', fn() { return this.myName; } }; ``` * `fn: function(){};` → `fn(){};` #### 物件內的變數 ```javascript= // 縮寫前 const name = "小明"; function fn() { return this.name; }; const person = { name: name, // 縮寫物件屬性名稱 fn: fn }; console.log(person,person.fn()); // {name: '小明', fn: ƒ} // 縮寫後 const name = "小明"; function fn() { return this.name; }; const person = { name, fn }; console.log(person,person.fn()); // {name: '小明', fn: ƒ} ``` * 在物件字面值的概念中,若屬性名稱與傳入的變數名稱相同(或函式),留下一個名稱即可,稱為「物件屬性名稱縮寫」。 * `name: name` → `name` ### 🔸 展開 #### 不同陣列合併 ```javascript= // 使用 concat(); const groupA = ['小明', '杰倫', '阿姨']; const groupB = ['老媽', '老爸']; const groupC = groupA.concat(groupB); console.log(groupC); // ['小明', '杰倫', '阿姨', '老媽', '老爸'] // 使用展開運算符 const groupA = ['小明', '杰倫', '阿姨']; const groupB = ['老媽', '老爸']; const groupC = [...groupA, ...groupB]; console.log(groupC); // ['小明', '杰倫', '阿姨', '老媽', '老爸'] ``` * 在 ES6 之前,會使用 `concat();` 方法合併不同陣列,但使用展開運算符 `...` 可以讓這個過程變得更加簡潔。 #### 物件擴展(淺拷貝) ```javascript= // 原本的物件 const methods = { fn1() { console.log(1); }, fn2() { console.log(1); }, } console.log(methods); // {fn1: ƒ, fn2: ƒ} // 新建立的物件 const newMethods = { ... methods, fn3() { console.log(1); } } console.log(newMethods); // {fn1: ƒ, fn2: ƒ, fn3: ƒ} ``` * 展開運算符具有淺拷貝的特性,新增的新屬性不會影響原物件。 #### 轉成純陣列 ```javascript! const doms = document.querySelectorAll('li'); console.log(doms); // 此時的 Prototype 為 NodeList 請轉為 Array const newDoms = [...doms]; console.log(newDoms); // Prototype 為 Array newDoms.map((e) => { // 可以使用了 ! console.log(e) }); ``` * 展開 `doms` 會看到 Prototype 為 NodeList,能使用的方法比較少 例如無法使用 `map()`。 * 使用展開運算符轉為陣列後,Prototype 為 Array,就能使用 `map()` 了! #### 參數預設值 ```javascript= // 未設定參數預設值 function sum(a, b) { return a + b; } console.log(sum(1)); // NaN // 設定參數預設值 // 1. 只寫一個參數 function sum(a, b = 1) { return a + b; } console.log(sum(1)); // 2 // 2. 兩個參數都有寫 function sum(a, b = 1) { return a + b; } console.log(sum(1, 5)); // 6 ``` * 有些情況,不一定所有參數都要填寫時,可設定參數預設值,避免錯誤。 * 參數預設值的寫法:`(a, b = 1)` 表示 b 的預設值為 1,若使用者為填寫參數時,會自動帶入 1;有填寫參數時,則會覆蓋預設值的設定。 ### 🔸 解構 #### 物件取值 ```javascript= // 未使用解構取值 const name = person.name; const age = person.age; console.log(name, age); // 小明 16 // 使用解構取值 const {name, age} = person; console.log(name, age); // 小明 16 ``` * `const { 屬性名稱 } = 物件;` #### 陣列取值 ```javascript= const array = ['小明', '杰倫', '漂亮阿姨']; const [a, b, c] = array; console.log(a, b, c); // 小明 杰倫 漂亮阿姨 ``` * `const [ 變數名稱 ] = 陣列;` * 陣列解構時,`[]` 內是變數宣告,值會依位置順序帶入變數。 #### 解構 + 展開 ```javascript= const people = { Ming: { name: '小明', age: 16, like: '鍋燒意麵' }, Jay: { name: '杰倫', age: 18, like: '跑車' }, Auntie: { name: '漂亮阿姨', age: 22, like: '名牌包' } } // 目標 // 1. Ming 為一筆資料 // 2. Jay + Auntie 為一筆資料 const { Ming, ...other } = people; console.log(Ming, other); // {name: '小明', age: 16, like: '鍋燒意麵'} {Jay: {…}, Auntie: {…}} // 3. 解構小明的年齡與喜歡的東西 const {age, like} = Ming; console.log(age, like); // 16 '鍋燒意麵' ``` * `const { Ming, ...屬性名稱 } = people;` #### 重新命名 ```javascript= // 前面已使用 Ming 作為變數名稱 let Ming = {}; // 解構時 不能再用 Ming 作為名稱 const { Ming: newMing } = people; console.log(newMing); ``` * `const { 原本的屬性名稱: 重新命名的屬性名稱 } = people;` * 使用 `:` 重新命名。 #### 函式參數 ```javascript= const person = { name: '小明', age: 16, like: '鍋燒意麵' } // 解構前 function callName (params) { console.log(params.name, params.age) }; callName(person); // 小明 16 // 解構後 function callName ({name, age}) { console.log(name, age) }; callName(person); // 小明 16 ``` * `()` 加入 `{}` 解構物件資料。 #### 解構在 React 的運用 ```jsx= const { useState } = React; function Card() { return (<div className="card"> <div className="card-body"> </div> </div>) } function App() { const [person, setPerson] = useState({ name: '小明', }) return ( <div> 我是{person.name} <Card person={person} ></Card> </div> ) } ``` * `const { useState } = React;` * 從 React 這個物件中,取出名為 `useState` 的屬性,並宣告成同名變數。 * `const [person, setPerson] = useState({ ... ,})` * `useState()` 會回傳一個陣列,使用陣列解構將第 0、1 個值分別指定給 `person` 與 `setPerson`。 ### 🔸 箭頭函式 #### 寫法 ```javascript= // 改寫前 function fn(a, b) { return a * b; } console.log(fn(2, 4)); // 8 // 改寫後 const fn = (a, b) => { return a * b; } console.log(fn(2, 4)); // 8 // 當回傳的內容是物件 // 請加上 () // 避免程式碼誤認 {} 為區塊主體 導致傳出的值為 undefined // 未加上 () const fn = () => { name: "小明" }; console.log(fn()); // undefined // 加上 () const fn = () => ({ name: "小明" }); console.log(fn()); // {name: '小明'} ``` * **箭頭函式不是語法糖**,但有許多縮寫的特性。 * 是函式表達式,因此函式一定會宣告變數。 #### this 的變化 ```javascript= // 傳統函式 var name = '全域' const person = { name: '小明', callName: function () { console.log('1', this.name); // 1 小明 setTimeout(function () { console.log('2', this.name); // 2 全域 console.log('3', this); // 3 Window }, 10); }, } person.callName(); // 箭頭函式 var name = '全域' const person = { name: '小明', callName: function () { console.log('1', this.name); // 1 小明 setTimeout(() => { // 這裡改成箭頭函式 console.log('2', this.name); // 2 小明 console.log('3', this); // 3 小明物件 }, 10); }, } person.callName(); ``` * 箭頭函式的 `this` 指向,會跟當前外層作用域是一致的。 * React Hook 寫法不太會遇到 `this` 的問題,這裡可以初步認識就好。 #### 縮寫 ```javascript= // 縮寫前 const newPeopleList = peopleList.map((person, key) => { const templateString = `<li>${person.userName}喜歡吃${person.like}</li>`; return templateString; }); const ul = document.querySelector("ul"); ul.innerHTML = newPeopleList.join(""); // map 會用逗號隔開資料 可使用 join("") 去除逗號 // 縮寫後 const newPeopleList = peopleList.map((person, key) => `<li>${person.userName}喜歡吃${person.like}</li>`); const ul = document.querySelector("ul"); ul.innerHTML = newPeopleList.join(""); // 再縮寫 // 不再另外宣告變數 直接將程式碼移過去?! const ul = document.querySelector("ul"); ul.innerHTML = peopleList.map((person, key) => `<li>${person.userName}喜歡吃${person.like}</li>`).join(""); ``` * 因箭頭函式自帶 `return`,故可以進行以下縮寫: * `{}` 內只有一行的情況。 * 刪除 `return`。 * 刪除 `{}`。 * 調整成同一行。 * 當參數只有一個的時候,可以將 `()` 刪除: ```javascript= const fn = a => a; console.log(fn(5)); // 5 ``` * 在 React 的環境下會處理掉逗點,故可不用使用 `.join("")`。 #### 箭頭函式在 React 的運用 ```jsx= // 改寫前 function Card() { return <div className="card"> <div className="card-body">這是一張卡片</div> </div> } function App() { return ( <div> <Card></Card> </div> ) } // 改寫後 const Card = () => { return <div className="card"> <div className="card-body">這是一張卡片</div> </div> } const App = () => { return ( <div> <Card></Card> </div> ) } ``` ### 🔸 陣列迴圈處理方法 #### 操作同一個陣列內的值 ```javascript= const people = [ { name: '卡斯伯', like: '鍋燒意麵', price: 80 }, { name: '小明', like: '牛肉麵', price: 120 }, { name: '漂亮阿姨', like: '滷味切盤', price: 40 }, { name: 'Ray', like: '大麻醬乾麵', price: 60 } ]; people.forEach((item) => { item.newPrice = item.price * 0.8; }); console.log(people); // {...newPrice: 64} {...newPrice: 96} {...newPrice: 32} ...newPrice: 48 ``` * `forEach()` 不會產生新陣列,而是影響原本的陣列。 #### 建立新陣列 ```javascript= const order = people.map((item) => { return { item: item.like, price: item.price * 0.8 } }); console.log(order); // {item: '鍋燒意麵', price: 64} ... // 縮寫 // 1. 刪除 {}、return // 2. 補上 () const order = people.map((item) => ({ item: item.like, price: item.price * 0.8 }) ); console.log(order); // {item: '鍋燒意麵', price: 64} ... ``` * `map()` 會產生新陣列,並不影響原本的陣列。 #### 使用 map() 的注意事項 ```javascript= const people = [ { name: '卡斯伯', like: '鍋燒意麵', price: 80 }, { name: '小明', like: '牛肉麵', price: 120 }, { name: '漂亮阿姨', like: '滷味切盤', price: 40 }, { name: 'Ray', like: '大麻醬乾麵', price: 60 } ]; // 即使不回傳值 也會回傳 undefined const order = people.map((item) => { if (item.price > 60) { return { item: item.like, price: item.price * 0.8 } } }); console.log(order); // [{…}, {…}, undefined, undefined] // 解法 // 1. 先使用 filter 判斷結果 // 2. 再使用 map() 產生新陣列 const filterOrder = people.filter((item) => item.price > 60); const order = filterOrder.map((item) => ({ item: item.like, price: item.price * 0.8 })); console.log(order); // {item: '鍋燒意麵', price: 64} {item: '牛肉麵', price: 96} // 縮寫 const order = people.filter((item) => item.price > 60) // 注意這裡沒 ; .map((item) => ({ item: item.like, price: item.price * 0.8 })); console.log(order); // {item: '鍋燒意麵', price: 64} {item: '牛肉麵', price: 96} ``` * `map()` 的回傳數量會等於原始陣列的長度,若遇到不回傳(例如未符合條件),也會回傳 `undefined`。 #### `map()` 在 React 中的運用 ```jxs= const App = ()=> { const people = [ { name: '卡斯伯', like: '鍋燒意麵', price: 80 }, { name: '小明', like: '牛肉麵', price: 120 }, { name: '漂亮阿姨', like: '滷味切盤', price: 40 }, { name: 'Ray', like: '大麻醬乾麵', price: 60 } ]; return <div> <ul> { people.map((item, i) => { return <li key={i}>{item.name}喜歡{item.like}</li> }) } </ul> </div> } ``` ### 🔸 JSX 章節作業優化 ```javascript= function App() { const products = [ { imageUrl: "https://images.unsplash.com/photo-1520013573795-38516d2661e4?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1536&q=80", title: "強力牙刷", price: 99, link: "https://www.google.com/?hl=zh_TW" }, { imageUrl: "https://images.unsplash.com/photo-1618038483079-bfe64dcb17f1?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80", title: "海綿", price: 10, link: "https://www.google.com/?hl=zh_TW" }, { imageUrl: "https://images.unsplash.com/photo-1546552696-7d5f4e89b0e8?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80", title: "掛勾", price: 100, link: "https://www.google.com/?hl=zh_TW" } ]; return <> <div className="row row-col-3"> { products.map((item,i) => (<div className="col" key={i}> <a href={item.link} className="card"> <img className="card-img-top object-fit" src={item.imageUrl} alt="" /> <div className="card-body"> <span className="text-dark">{item.title}</span> <span className="float-end">${item.price}</span> </div> </a> </div>)) } </div> </> } ReactDOM.createRoot(document.querySelector("#root")).render(<App />); ``` * HTML 結構只要寫一次就好,剩下的迴圈會根據有幾筆資料,就產生幾次結構。 ### 🔸 物件參考特性 #### 物件是以傳參考的形式賦值 ```javascript= const person = { name: '小明', obj: {} }; const person2 = person; person2.name = "杰倫"; console.log(person2); // {name: '杰倫', obj: {…}} const { obj } = person; obj.name = "person 下的物件"; console.log(person.obj.name); // person 下的物件 ``` #### 物件作為參數 ```javascript= const fn = (item) => { item.name = '杰倫'; }; const person = { name: '小明', obj: {} }; fn(person); console.log(person.name); // 杰倫 ``` * 物件作為參數傳遞,一樣具備傳參考特性。 #### 淺層拷貝 ```javascript= const person = { name: '小明', obj: {} } const person2 = { ...person }; person2.name = "杰倫"; // 指向不同記憶體位置 person2.obj.name = " person2 的值"; // 還是指向相同記憶體位置 console.log(person, person2); // {name: '小明', obj: {…}} {name: '杰倫', obj: {…}} ``` * 可以使用展開運算符進行淺層拷貝。 * 由於拷貝出來的物件,只有第一層指向的記憶體位置不同,再下一層(`obj`)的記憶體位置還是相同,故稱為淺層拷貝。 * 淺層拷貝的語法較精簡,若確認需求不會動到深層物件,可使用淺層拷貝處理資料即可。 #### 深層拷貝 ```javascript= const person = { name: '小明', obj: {} }; const person2 = JSON.parse(JSON.stringify(person)); person2.name = "杰倫"; // 指向不同記憶體位置 person2.obj.name = " person2 的值"; // 指向不同記憶體位置 console.log(person, person2); ``` * 作法: 1. 使用 `JSON.stringify();` 先轉成字串。 2. 再使用 `JSON.parse()` 轉回物件。 ### 🔸 非同步與 Promise #### 非同步的觀念 ```javascript= function getData() { setTimeout(() => { console.log('... 已取得遠端資料'); }, 0); } // 請問取得資料的順序為何 function init() { console.log(1); getData(); console.log(2); } init(); // 1, 2, ... 已取得遠端資料 ``` * 所有非同步操作都會最後執行,但無法控制執行順序,因此出現了 Promise 解決傳統非同步語法難以建構及管理的問題。 #### Promise ```javascript= // 基本結構 const promiseSetTimeout = (status) => { return new Promise((resolve, reject) => { setTimeout(() => { if (status) { resolve('promiseSetTimeout 成功') } else { reject('promiseSetTimeout 失敗') } }, 0); }) }; // 使用 .then 接收結果 promiseSetTimeout(1) .then((res) => { console.log(res); // promiseSetTimeout 成功 }); // 串接多個 Promise 結果 promiseSetTimeout(1) .then((res) => { console.log(res); console.log(1); return promiseSetTimeout(1); // 使用 return 傳出結果 }) .then((res) => { // 再次使用 .then 接收 console.log(res); console.log(2); }) // 使用 .catch 捕捉錯誤 promiseSetTimeout(0) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); // promiseSetTimeout 失敗 }) ``` * 基於 Promise 建構的函式。 * `const promiseSetTimeout = (status) => { ... };` 傳入參數,決定結果是成功或失敗。 * `return new Promise((resolve, reject) => { ... };` 建構 Promise 的程式碼。 * `resolve`:成功。 * `reject`:失敗。 * 若直接執行 `promiseSetTimeout();` 會看不到結果,因為必須使用 `.then` 接收結果。 * 接收多個 Promise 的結果時,請避免使用巢狀結構接收,而是使用 `return` 傳出結果後,再使用 `.then` 接收。 * 若在「等待 Promise 結果」且「沒有捕捉錯誤」的情況,會中斷同一區塊中(`{}`)程式碼的執行,因此需要設定捕捉錯誤的方法(`catch`)。 #### 實戰運用 ```javascript= axios.get("https://randomuser.me/api/") .then((res) => { console.log(res); return axios.get("https://randomuser.me/api/") }) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); }) ``` * [axios](https://github.com/axios/axios) * [Random User Generator](https://randomuser.me/) * axios 是基於 Promise 開發的,故可以使用 `.then` 跟 `.catch` 的方法處理回傳結果。 ### 🔸 Async / Await #### 當資料有順序性時 ```javascript= // 1. 取得 seed ( seed 可產生同一筆用戶資料 ) // 2. 再次取得同一筆用戶 axios.get("https://randomuser.me/api/") .then((res) => { console.log(res); const { seed } = res.data.info return axios.get(`https://randomuser.me/api/?seed=${seed}`) }) .then((res) => { console.log(res); // 與上方為同一筆用戶資料 }) ``` * [Random User Generator/Seeds](https://randomuser.me/documentation#seeds) #### 使用 Async / Await 改寫 ```javascript= const asyncFn = async () => { const res = await axios.get("https://randomuser.me/api/"); const { seed } = res.data.info; const res2 = await axios.get(`https://randomuser.me/api/?seed=${seed}`); console.log(res, res2); }; asyncFn(); ``` * Async / Await 是 Promise 的語法糖,因此對 Promise 越熟悉,有助於使用 Async / Await。 * `async` 標記一個函式為「非同步函式」。 * `await` 運算子用於等待 Promise 的結果。它將非同步邏輯以同步語法呈現,取代了傳統的 `.then()` 鏈式調用,顯著提升代碼的可讀性。 * 注意 `async` 只有一個作用域 `{}`,故變數命名不能重複。 #### 立即函式 ```javascript= // 基本結構 // (function() { // console.log(1); // })(); // 改寫 (async () => { const res = await axios.get("https://randomuser.me/api/"); const { seed } = res.data.info; const res2 = await axios.get(`https://randomuser.me/api/?seed=${seed}`); console.log(res, res2); })(); ``` ### 🔸 Async / Await 實戰運用 #### 取得 Async function 回傳值的方法 ```javascript= const asyncFn = async () => { const res = await axios.get('https://jsonplaceholder.typicode.com/todos/1'); res.data.name = "小花"; // 新增資料 return res; }; asyncFn() .then(res => { console.log(res); }); ``` * 在 `async` 函數內部使用 `await` 取代 `.then()`。 * 但在外層,依舊可使用 `.then()` 接收結果。 * Async / Await 與 Promise 通常會擇一使用,但也可以交互使用。 #### Async function 錯誤處理 ```javascript= const asyncFn = async () => { try { const res = await axios.get("https://jsonplaceholder.typicode.com/todos/1") return res; } catch (error) { throw error; // 拋出錯誤結果 } }; asyncFn() .then(res => { console.log("Promise:", res); }) .catch(err => { console.log("Promise Error:", err); }) ``` * `try...catch` 是一個程式設計中的錯誤處理結構。 * `throw` 是一種程式控制結構,用於在程式中拋出錯誤,並中斷當前執行流程。 #### Async Function 與 React ```jsx= const App = () => { const [data, setData] = React.useState({}) React.useEffect(() => { (async () => { const res = await axios.get('https://jsonplaceholder.typicode.com/todos/1'); console.log(res); setData(res.data); // React 的方法 })(); }, []); return <div> {data.title} {/* React 的方法 */} </div> } ReactDOM .createRoot(document.getElementById('root')) .render(<App/>) ``` ### 🔸 模組化 JavaScript:ESModule #### 基礎知識 ```javascript= // 匯出 export default function fn() { console.log(1); }; // 匯入 import fn from "./fnModule.js"; fn(); ``` * 將函式模組化,以利重複調用(會將模組存在另一個檔案裡,再透過引入的方式匯入主要開發的檔案裡)。 * 運作概念基本上分為匯出與匯入。 * `<script>` 的 `type` 需要加入 `module` 模組才能運作,但會與 `type="text/babel"` 相衝,故課程前期不會用到 ESModule 定義元件。 * JavaScript 檔案放在 CDN 上提供時,檔名最好明確以 `.js` 結尾。 * CDN 依副檔名判斷檔案類型。 * `.js → application/javascript` #### 預設匯出 ```javascript= export default { myName: "小花", fn() { console.log("我是小花"); } }; ``` * 關鍵字 `export default`。 * 每個檔案裡只能有一個預設匯出。 * 不用為模組命名。 * 是常見的匯出方式,通常用於匯出物件,在 React 開發中可用來匯出元件。 #### 預設匯入 ```javascript= import obj from "./fnModule.js"; console.log(obj); // {myName: '小花', fn: ƒ} obj.fn(); // 我是小花 ``` * 因為預設匯出時不用命名,所以預設匯入時,可以為模組命名。 #### 具名匯出 ```javascript= export const myName = "小花"; export function fn() { console.log("我是小花"); } ``` * 關鍵字 `export`。 * 每個檔案裡可以有多個具名匯出。 * 要為模組命名(自定義名稱)。 * 可用於匯出已定義的變數、物件、函式,專案開發中通常用於「方法(函式)匯出」。 * 第三方的框架、函式、套件很常使用具名定義「方法(函式)」。 #### 具名匯入(單一匯入) ```javascript= import { fn } from "./fnModule.js"; fn(); // 我是小花 ``` * 使用解構匯入模組。 * 建議的匯入方法。 #### 具名匯入(全部匯入) ```javascript= import * as allFn from "./fnModule.js"; console.log(allFn); // 會將 fnModule.js 所有方法全部匯入 allFn.fn(); // 調用其中的模組 ``` * `import * as 自定義名稱 from ... ;` * 不建議的匯入方法,錯誤較難以察覺(要逐一調查是哪個模組出錯)。 #### SideEffect ```javascript= // 匯出 (function (global) { global.$ = '我是 jQuery'; })(window); // 匯入 import "./sideEffect.js" console.log($); ``` * `sideEffect.js` 的目的是在被匯入時**立刻執行程式碼**,因此通常不會 `export`,而是立即函式或正在執行的傳統函式。 * 是早期函式庫的匯入方法,例如 jQuery,目前較少使用。