`#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);
```
:::
### 🔸 核心概念

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 管理套件。
* 依賴關係與版本更可控。
### 🔸 環境建立

```
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
### 🔸 學習流程

### 🔸 建構方法
* **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&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2352&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,目前較少使用。