|
|
爽快的墨镜 · 能否直接将React ...· 4 月前 · |
|
|
很拉风的小狗 · React中如何在FileReader的on ...· 3 月前 · |
|
|
难过的大熊猫 · 使用yarn+env-cmd时,如何在cre ...· 2 月前 · |
|
|
英俊的海龟 · PHP连接数据库成功,但是无法获取数据!_p ...· 2 年前 · |
|
|
活泼的伤疤 · 教育社会学_社会学视野网· 2 年前 · |
|
|
任性的火车 · Docker容器中的/etc/resolv. ...· 3 年前 · |
|
|
儒雅的保温杯 · input type checkbox ...· 3 年前 · |
|
|
酷酷的手术刀 · 使用xlwings从本工作簿中调用vba宏· 3 年前 · |
本篇將深入探討 React 19 的核心概念與實作技巧,從 Vite 專案結構開始,逐步介紹 JSX 語法、元件設計、Props 傳遞、事件處理到狀態管理。透過實際範例,你將學會如何建構可重用的元件、管理應用程式狀態,以及掌握現代 React 開發的最佳實踐。
在 Vite 專案中,
index.html
是整個應用的入口點,它只包含一個掛載點
<div id="root"></div>
和一個 JavaScript 模組引入
<script type="module" src="/src/main.jsx"></script>
。React 會在這個掛載點渲染整個應用程式。
<!doctype html> |
為什麼使用
.jsx
副檔名?
.jsx
副檔名明確告訴 Vite 這個檔案包含 JSX 語法,需要進行特殊處理。雖然 Vite 也能處理
.js
檔案中的 JSX,但使用
.jsx
能讓檔案用途更清晰,也方便編輯器提供更好的語法高亮和自動補全。
/src/main.jsx
是整個 React 應用的入口檔案,負責初始化並渲染根元件。讓我們逐步了解每個部分的作用:
📌 核心概念說明:
StrictMode(嚴格模式)
createRoot(建立根節點)
ReactDOM.render
createRoot(DOM 元素).render(React 元件)
CSS 匯入
模組匯入簡化
.jsx
副檔名
.js
、
.jsx
、
.json
檔案
import App from './App'
會自動找到
App.jsx
虛擬 DOM 渲染
render
方法會建立虛擬 DOM 樹
import { StrictMode } from 'react'; // 引入 StrictMode 用於開發環境的嚴格檢查 |
React 18+ 重要變更:
// ✅ React 18+ 正確寫法(啟用 Concurrent Mode) |
關鍵差異:
JSX 轉換改進(React 17+):
從 React 17 開始,你不再需要在每個檔案中
import React
:
// ✅ React 17+ 不需要這樣宣告 |
React 16 以前,因為 Babel 編譯 JSX 時會直接呼叫
React.createElement
,所以每個使用 JSX 的檔案都必須手動寫上
import React from 'react';
,確保 React 在作用域。自 React 17 起,Babel 或 SWC 等現代編譯器會自動注入轉換函式,讓你不必再手動引入 React,JSX 可以直接使用,開發流程更簡潔。
在 VSCode 中,你可以按住
Ctrl
並點擊
<App />
,快速跳轉到
App.jsx
檔案。這是 React 應用的主要元件,所有的 UI 邏輯都從這裡開始。
React 採用
元件樹(Component Tree)
的架構,每個元件都可以包含子元件,形成巢狀結構。在這個例子中,
App
是
main.jsx
的子元件,而
App
內部又可以包含更多子元件。每個元件都有自己獨立的邏輯、狀態和渲染輸出。
import { useState } from 'react'; // 匯入 useState Hook 用於狀態管理 |
📌 App.jsx 關鍵概念解析:
useState Hook(狀態管理)
const [count, setCount] = useState(0); |
count
:唯讀的狀態值,用於顯示
setCount
:更新狀態的函式,呼叫後會觸發重新渲染
0
:初始值,元件第一次渲染時的狀態
./assets/xxx
- 相對路徑(src 目錄下)
/xxx
- public 目錄下的檔案
函式元件結構
function ComponentName() {
|
JSX Fragment(空標籤
<>...</>
)
<React.Fragment>...</React.Fragment>
// ❌ 錯誤:回傳多個根元素 |
export default(預設匯出)
export default
import MyApp from './App'
export function App() {...}
target=”_blank” 安全性提醒:
使用
target="_blank"
開啟新分頁時,建議加上
rel="noopener noreferrer"
:
<a href="https://example.com" target="_blank" rel="noopener noreferrer"> |
noopener
:防止新頁面存取
window.opener
,避免潛在的安全風險
noreferrer
:不發送 referrer 資訊,保護隱私
React 19 的 JSX 會自動添加
noopener
,但明確寫出來更清楚。
在團隊協作或大型專案中,統一的程式碼風格和即時的錯誤檢查至關重要。Vite 預設已經配置好 ESLint,但我們需要進一步優化設定,並整合 Prettier 來自動格式化程式碼。
ESLint 程式碼品質檢查 檢測未使用的變數、潛在錯誤、不符合規範的寫法 Prettier 程式碼格式化 統一縮排、引號、分號、換行等排版風格最佳實踐組合:
eslint.config.js 配置檔解析:
import js from '@eslint/js'; // ESLint 核心 JavaScript 規則 |
ESLint 9+ 扁平化配置說明:
新的
eslint.config.js
格式更簡潔、更容易理解:
與舊格式的差異:
// ❌ 舊格式 (.eslintrc.js) |
光有 ESLint 配置檔還不夠,我們需要讓 VSCode 即時顯示錯誤提示。
步驟 1:安裝 VSCode ESLint 擴充套件
在 VSCode 擴充套件市場搜尋「 ESLint 」(by Microsoft),點擊安裝。
步驟 2:配置專案級設定
在專案根目錄建立
.vscode/settings.json
檔案,啟用自動修復和驗證:
{ |
專案設定共享:
建議將
.vscode/settings.json
加入 Git 版本控制,讓團隊成員有一致的開發環境設定。
在
.gitignore
中確保沒有忽略
.vscode/
目錄:
# .gitignore |
這樣其他人 clone 專案後,VSCode 會自動套用這些設定。
步驟 3:測試 ESLint 自動修復
在
src/App.jsx
故意添加多餘的分號:
function App() {
|
此時 ESLint 預設規則可能不會檢測這個錯誤。我們可以添加自訂規則:
rules: { |
儲存配置檔後,回到
App.jsx
,錯誤提示會立即出現:
按下 Ctrl + S 儲存,ESLint 會自動移除多餘的分號。
ESLint 自動修復的魔法:
ESLint 可以自動修復的問題類型:
無法自動修復的問題(需要手動處理):
Prettier 是一個固執己見的程式碼格式化工具,能自動統一程式碼風格,避免團隊成員因為排版風格不同而產生無意義的 Git diff。
步驟 1:安裝 Prettier 相關套件
# 安裝 Prettier 及整合套件 |
套件說明:
prettier
:Prettier 核心套件
eslint-config-prettier
:關閉 ESLint 中與 Prettier 衝突的格式規則
eslint-plugin-prettier
:將 Prettier 規則整合到 ESLint 中
-D 參數說明:
-D
是
--save-dev
的縮寫,表示安裝為
開發依賴
(devDependencies)。這些工具只在開發時使用,打包後的生產環境不需要,可以減少最終打包體積。
// package.json |
步驟 2:建立 Prettier 配置檔
在專案根目錄建立
.prettierrc
檔案:
{ |
Prettier 配置選項說明:
語句結尾是否加分號
const x = 1;
vs
const x = 1
singleQuote
false
字串使用單引號
'hello'
vs
"hello"
tabWidth
縮排空格數
2 或 4
printWidth
每行最大字元數
80、100、120
trailingComma
"es5"
尾逗號規則
{ a: 1, }
bracketSpacing
物件括號內空格
{ foo }
vs
{foo}
arrowParens
"always"
箭頭函式括號
(x) => x
vs
x => x
React 專案推薦配置:
singleQuote: true
):更簡潔
jsxSingleQuote: false
):符合 HTML 慣例
semi: true
):避免潛在錯誤
printWidth: 100
):平衡可讀性和寬螢幕利用率
步驟 3:整合 Prettier 到 ESLint
修改
eslint.config.js
,將 Prettier 整合進來:
import js from '@eslint/js'; |
避免 ESLint 和 Prettier 衝突:
由於 Prettier 會自動處理所有格式相關的問題(縮排、分號、引號等),我們應該讓 Prettier 完全接管格式化工作,ESLint 只負責程式邏輯檢查。
no-extra-semi
等格式規則
prettier/prettier
規則
eslint-config-prettier
自動關閉衝突規則
步驟 4:更新 VSCode 設定
更新
.vscode/settings.json
,設定 Prettier 為預設格式化工具:
{ |
現在每次按 Ctrl + S 儲存時,會自動執行:
步驟 5:批次格式化整個專案
在
package.json
添加格式化腳本:
{ |
執行格式化命令:
pnpm format |
Prettier 會掃描
src
目錄下的所有
.js
、
.jsx
、
.css
、
.json
檔案並自動格式化。
ESLint + Prettier 完美組合總結:
ESLint 程式邏輯錯誤、最佳實踐、React 規則 即時檢查 + 儲存時修復 Prettier 程式碼格式(縮排、引號、分號等) 儲存時自動格式化工作流程:
pnpm format
確保所有檔案格式一致
環境配置完成後,讓我們深入學習 React 的核心語法和開發模式。
// ✅ 正確:自閉合標籤必須加斜線 |
2. 必須回傳單一根元素
// ❌ 錯誤:回傳多個並列元素 |
Fragment 的使用時機:
<Fragment>
或簡寫
<>
用於包裹多個元素但不產生額外 DOM。
// 使用 Fragment(不產生 DOM) |
何時需要 key?
<>
簡寫不支援 key 屬性
<Fragment key={...}>
完整寫法
3. JavaScript 表達式需用
{}
包裹
function Greeting({ name, age }) { |
4. HTML 屬性的 JSX 寫法
由於 JSX 是 JavaScript,某些 HTML 屬性名稱需要調整:
class
className
class
是 JavaScript 保留字
htmlFor
for
是 JavaScript 保留字
onclick
onClick
事件處理採用駝峰命名
onchange
onChange
事件處理採用駝峰命名
tabindex
tabIndex
屬性採用駝峰命名
data-id
data-id
data-* 和 aria-* 保持 kebab-case
// ✅ 正確的 JSX 寫法 |
5. 內聯樣式使用物件
function StyledComponent() {
|
6. 多行 JSX 需用
()
包裹
// ✅ 正確:多行 JSX 用括號包裹 |
function UserGreeting({ isLoggedIn, username }) { |
條件渲染的陷阱:
// ❌ 危險:數字 0 會被渲染出來 |
如果有大量 HTML 需要轉換成 JSX,可以使用官方提供的 HTML to JSX 線上轉換器 。
JSX 最佳實踐:
className
而非
class
onClick
、
onChange
)
讓我們應用剛學到的 JSX 知識,重構
App.jsx
,展示 JSX 的靈活性:
import { useState } from 'react'; |
範例重點:
myBr
、
imgVite
、
h1Element
)
{變數名}
data-*
屬性保持 kebab-case 寫法
rel="noopener noreferrer"
提升安全性
為什麼要把 JSX 存成變數?
// 範例:條件渲染不同的 JSX 變數 |
React 使用 JSX 描述 UI,但瀏覽器只認識真實的 DOM 節點。React 透過以下流程將 JSX 轉換成實際的網頁內容:
graph LR A["JSX 代碼"] B["React 元素物件"] C["虛擬 DOM"] D["Diff 演算法"] E["更新實體 DOM"] A --> B --> C --> D --> E
關鍵角色:
ReactDOM.createRoot
react-dom/client
提供的
createRoot
API 負責:
import { StrictMode } from 'react'; |
分步解析:
document.getElementById('root')
- 找到 HTML 中的掛載點
createRoot(...)
- 建立 React 根節點
root.render(...)
- 將元件樹渲染到 DOM
在特殊情況下,你可能需要在同一個頁面的不同位置渲染獨立的 React 應用:
<body> |
import { StrictMode } from 'react'; |
多根節點的注意事項:
推薦做法:
如果需要在頁面不同位置顯示內容,應該在單一根節點中透過元件組合完成:
// ✅ 推薦:單一根節點,透過元件組合 |
// ❌ React 17 舊寫法(已不推薦) |
新 API 的優勢:
渲染機制總結:
createRoot
和
render
通常只在應用啟動時呼叫一次
useState
、
useReducer
等 Hook 觸發
元件是 React 應用的基本建構單元,就像樂高積木一樣,可以組合成完整的應用程式。元件封裝了:
元件可以小到一個按鈕,也可以大到整個頁面。
自從 React 16.8 推出 Hooks 開始,官方就推薦優先使用 函式元件 + Hooks 這種現代開發方式。
// 1. 函式宣告式 |
元件命名規則:
在撰寫 JSX 時,React 會根據標籤名稱自動判斷該標籤應被解讀為「React 元件」還是「原生 HTML 標籤」。
以大寫字母開頭
的標籤會被視為 React 元件,而
小寫字母開頭
的則會解讀為原生 HTML 標籤。若不小心將自訂元件以小寫命名,React 會嘗試尋找相應的 DOM 標籤,造成錯誤(找不到對應 HTML 標籤)。因此,請務必遵守元件命名規則,確保 React 能正確渲染元件。
Welcome
、
MyButton
、
UserProfile
)
welcome
、
myButton
)- React 會當成 HTML 標籤
// ✅ 正確:大寫開頭,React 知道這是元件 |
元件名稱必須大寫,用來區分 React 元件和 HTML 標籤:
// React 元件(大寫) |
useState
Hook
this.state
副作用處理
useEffect
Hook
生命週期方法
this 綁定
React 19 支援
✅ 完整支援
⚠️ 維護模式
// ✅ 現代寫法:函式元件 + Hooks |
Class 元件的未來:
雖然 React 仍支援 Class 元件,但官方已明確表示:
本課程完全使用現代的函式元件方式教學。
React 強調「小元件組合而成大型介面」的設計理念。能否妥善組織元件結構、避開常見錯誤,將直接影響開發的可維護性與效能。
觀念重點:
Header
若定義在元件外層,函式只建立一次,每次重新渲染只是重跑函式內容。子元件會隨父元件一起重新渲染,但元件實例(狀態)保持不變。
App
內部重新宣告
Header
,每次
App
渲染時,
Header
也會被認為是全新的,React 會強制重新掛載(re-mount),造成狀態丟失與效能浪費。
什麼是重新渲染?什麼是重新掛載?
元件必須定義在外部作用域,切勿在其他元件內部宣告。這有助於狀態穩定與執行效能最佳化。
✅ 正確:扁平化定義元件
// 所有元件定義在同一層級外部 |
❌ 錯誤:巢狀定義元件
function App() {
|
function Logo() {
|
元件樹結構:
App |
讓我們將原本的 Logo 區域獨立成多個小元件,展示元件組合的威力:
import { useState } from 'react'; |
元件拆分的優勢:
ImgA
、
LinkA
可以在其他地方使用
ImgA
或
ImgB
元件組合結構:
App |
元件拆分原則:
何時該拆分元件?
在 React 中,Props(properties 的縮寫)是元件之間傳遞資料的主要方式。就像函式可以接收參數一樣,React 元件可以接收 props 並根據這些資料來決定渲染的內容。
唯讀(Read-Only)
單向資料流
// 1. 最基本的 props 使用方式 |
當我們需要傳遞多個 props 時:
// 2. 傳遞多個 props |
注意事項:
name="Alice"
age={25}
true
可以省略值:
isActive
等同於
isActive={true}
在 React 中,我們常常使用解構賦值(Destructuring)來簡化 props 的使用。讓我們看看如何逐步改善程式碼:
// 1. 基本寫法:使用 props 物件 |
React 的 props 可以傳遞各種資料類型,讓我們逐一看看:
// 1. 基本型別 |
Props 使用的最佳實踐
on
開頭:
onClick
、
onChange
is
、
has
開頭:
isActive
、
hasError
預設值處理
function UserCard({ name = "訪客", age = 0 }) { |
避免過度傳遞
function UserProfile({ |
Props 語法注意事項:
字串可以不用大括號 :
<Greeting name="Alice" /> // ✅ 字串可以不用大括號,也可以用大括號包住(但不必)。 |
布林值簡寫 :
<Button disabled={true} /> // 完整寫法 |
JSX 作為 Props :
<Card header={<h1>Title</h1>} /> |
讓我們改寫之前的範例,我們可以把 Img 的 src 值與 alt 值,利用 props 來傳遞給元件。但此時應該會出現 ESLint 的錯誤。
import { useState } from 'react'; |
由於 Vite 的環境有 ESLint 而檢查預設開啟,因此我們必需對這兩個錯誤的元件定義 props 的型別以及是否必存在。透過套件 prop-types 來導入使用。
prop-types 是什麼?
prop-types
是一個 React 官方維護的第三方套件,用來在開發階段「檢查元件 props 的型別和必填性」。透過設置每個元件的
propTypes
屬性,我們可以定義每個 props 應該是什麼型別(例如:
string
、
number
、
array
、
object
),以及是否一定要傳遞。這能幫助開發時及早發現資料型別錯誤,提升專案的穩定性與可維護性。
常見用途:
注意:
prop-types 只會在開發模式下提供警告,生產環境(正式 build) 不會影響效能。如果你使用 TypeScript,則不需要再用 prop-types,因為型別檢查已被 TypeScript 覆蓋。
import PropTypes from 'prop-types'; |
當然你可以關閉 ESLint 對 props 型別的檢查,大多數現在的新專案都會使用 TypeScript 來開發輔助錯誤檢查。只有以前的舊專案,在沒有導入 TypeScript 又需要輔助檢查錯誤時才可能利用去 props-types 做檢查。
rules: { |
現在不需要 props-type 也不會發生錯誤了。
React 的一大特色是可以將小元件組合成更大的元件。透過這種組合方式,我們可以:
讓我們透過實際範例,一步步學習如何組合元件:
步驟 1:建立基礎圖片元件
// 1. 建立可重用的圖片元件 |
步驟 2:優化元件結構
觀察上面的程式碼,我們可以發現
LinkA
和
LinkB
結構非常相似,只是傳入的資料不同。
讓我們將它們合併成一個更通用的
MyLink
元件:
// 1. 基礎圖片元件(保持不變) |
步驟 3:資料與元件的分離
在實際開發中,資料通常會從 API 或配置檔案中獲取。讓我們將資料與元件分離,使程式碼更容易維護:
// 1. 基礎圖片元件(使用物件解構) |
元件組合的最佳實踐
單一職責原則
程式碼組織
讓我們看看如何將元件拆分到不同檔案:
步驟 1:建立元件檔案結構
src/ |
步驟 2:撰寫各個元件檔案
// 1. 預設匯入:不需要大括號 |
// 1. 資源匯入 |
// 1. 命名匯入 |
// 命名匯出 |
步驟 3:使用路徑別名(Path Alias)
在大型專案中,我們常常需要處理複雜的檔案路徑。路徑別名可以幫助我們:
設定路徑別名的步驟
編輯 vite.config.js
import { defineConfig } from 'vite'; |
使用別名匯入
// 舊寫法:使用相對路徑 |
實際應用範例:
// 使用路徑別名匯入資源 |
匯出匯入的最佳實踐
換行符號錯誤處理
如果發生大量的
Delete ␍
錯誤,這是**換行符號 (Line Ending)**的問題。
問題原因:
這個問題通常發生在
執行 Prettier 格式化
(
pnpm format
或儲存時自動格式化)時:
CRLF ( \r\n )
作為換行符號
LF ( \n )
作為換行符號
App.jsx
檔案可能從 Windows 系統建立,使用了 CRLF 格式
CRLF
轉換為
LF
Delete ␍
錯誤 ( ␍ 代表 CR,即
\r
)
錯誤症狀:
# 執行 Prettier 格式化時會看到 |
解決方法:
方法 1:在 Prettier 配置中明確設定(推薦)
在
.prettierrc
檔案中添加
endOfLine
選項:
{ |
方法 2: VSCode 設定
在
.vscode/settings.json
中設定:
{ |
或直接在 VSCode 右下角點擊
CRLF
/
LF
切換為
LF
。
方法 3: Git 設定
# 所有平台統一設定 ( 推薦 ) |
團隊協作建議:
統一使用
LF
是
跨平台開發的業界標準
,所有平台 ( Windows、Mac、Linux ) 都能正確處理,不會造成困擾。
**步驟 1:建立
.editorconfig
檔案 ( 團隊統一標準 ) **
在專案根目錄建立
.editorconfig
,所有主流編輯器會自動讀取:
root = true |
步驟 2:確保 Prettier 配置一致
.prettierrc
中設定
"endOfLine": "lf"
,確保 Prettier 格式化時統一使用 LF。
步驟 3:團隊成員 Git 設定
所有團隊成員執行:
git config --global core.autocrlf false |
步驟 4:專案級別 Git 設定(可選)
在專案根目錄建立
.gitattributes
:
- text=auto eol=lf |
工作流程:
pnpm format
統一格式
pnpm format
自動轉換
.editorconfig
+
.prettierrc
+
.gitattributes
三重保障
在 React 中,我們經常需要將資料陣列轉換成元件列表來顯示。React 提供了簡單且強大的方式來處理列表渲染:
使用陣列直接渲染
使用 map 方法轉換
讓我們透過實際範例來學習列表渲染:
範例 1:使用 map 方法轉換陣列(缺少 key)
// 1. 簡單的字串陣列 |
重要:列表渲染中的 key
當我們執行上面的程式碼時,會在 console 看到警告:
Warning: Each child in a list should have a unique "key" prop. |
為什麼需要 key?
React 需要
key
屬性來識別列表中的每個元素,這樣才能:
key 的特性:
選擇 key 的原則:
讓我們修改程式碼,加入適當的 key:
範例 2:正確使用 key 屬性
讓我們修改程式碼,加入適當的 key:
// 1. 簡單的列表範例 ( 使用值本身作為 key ) |
map 方法的優點:
更好的效能
更好的可讀性
最佳實踐:
id: 1
,
id: 2
)
改進範例:
// 更好的做法:為資料添加唯一 ID |
列表渲染的進階技巧
{items.map(item => ( |
{items.map(item => ( |
{categories.map(category => ( |
現在讓我們把之前的 Logo 元件改寫成使用列表渲染的方式:
export default function MyLogo() {
|
這個改寫有幾個重點:
id
作為唯一識別
map
方法轉換資料
key
屬性避免警告
div
包裹確保返回單一根元素
myBr
變數
這樣的寫法不僅更簡潔且易於擴充,同時也符合 React 的基本原則:
在 React 中,
children
是一個特殊的 prop,它代表元件標籤之間的內容。這個機制讓我們能夠建立更靈活的可重用元件。
children prop 的三個重要特性:
為什麼需要 children?
想像一下,如果沒有 children,我們要建立一個通用的卡片元件:
// ❌ 不好的做法:需要定義很多 props |
使用 children 後:
// ✅ 好的做法:使用 children 更靈活 |
這就是為什麼我們需要 children:
讓我們透過實際的例子來了解如何使用 children prop:
/* |
然後在 App 元件中使用它:
import { useState } from 'react'; |
React 實作了一個跨瀏覽器的合成事件系統(SyntheticEvent),統一了各瀏覽器的事件處理差異,讓開發者不需要擔心瀏覽器相容性問題。
React 事件 vs HTML 事件
HTML 事件 React 事件 小寫
onclick
駝峰命名
onClick
字串
"alert('Hi')"
函式
{handleClick}
return false
e.preventDefault()
原生 Event
SyntheticEvent(跨瀏覽器)
// ❌ HTML 原生寫法 |
在 React 中,我們有三種主要的事件處理方式:
// 1. 內聯匿名函式(適合簡單邏輯) |
常見錯誤:不要立即執行函式!
// ❌ 錯誤:函式會立即執行 |
React 支援所有標準的 DOM 事件,這裡列出最常用的:
onClick
<button onClick={handleClick}>點擊</button>
onDoubleClick
<div onDoubleClick={handleDoubleClick}>雙擊</div>
onMouseEnter
<div onMouseEnter={handleEnter}>移入</div>
onMouseLeave
<div onMouseLeave={handleLeave}>移出</div>
onMouseMove
<div onMouseMove={handleMove}>移動</div>
onMouseDown
<button onMouseDown={handleDown}>按下</button>
onMouseUp
<button onMouseUp={handleUp}>放開</button>
onKeyDown
<input onKeyDown={handleKeyDown} />
onKeyUp
<input onKeyUp={handleKeyUp} />
onKeyPress
按鍵按下(已廢棄)
建議使用
onKeyDown
onChange
輸入值改變
<input onChange={handleChange} />
onSubmit
<form onSubmit={handleSubmit}>
onFocus
<input onFocus={handleFocus} />
onBlur
<input onBlur={handleBlur} />
onScroll
<div onScroll={handleScroll}>滾動</div>
onDrag
<div onDrag={handleDrag}>拖曳</div>
onDrop
<div onDrop={handleDrop}>放置</div>
onLoad
<img onLoad={handleLoad} />
onError
<img onError={handleError} />
實際範例:綜合事件處理
function EventDemo() {
|
React 事件處理函式會自動接收一個
SyntheticEvent
物件,它封裝了原生 DOM 事件,提供跨瀏覽器的一致性:
event.type
"click"
,
"change"
,
"keydown"
event.target
觸發事件的元素
<button>
,
<input>
event.currentTarget
綁定事件的元素
通常與
target
相同
event.clientX
/
event.clientY
滑鼠相對於視窗的座標
150
,
200
event.pageX
/
event.pageY
滑鼠相對於頁面的座標
150
,
200
event.screenX
/
event.screenY
滑鼠相對於螢幕的座標
150
,
200
event.preventDefault()
阻止預設行為
阻止表單提交、連結跳轉
event.stopPropagation()
阻止事件冒泡
防止事件向上傳播
實際範例:事件物件使用
function EventObjectDemo() {
|
React 不支援
return false
在 HTML 中可以用
return false
阻止預設行為,但在 React 中必須明確呼叫
preventDefault()
:
// ❌ HTML 寫法(React 無效) |
讓我們透過實際的專案範例來學習事件處理的應用。我們將建立一個按鈕元件和表單元件,展示不同的事件處理方式。
步驟 1:建立按鈕元件
const handleClick = () => console.log('is click event!!');
|
這個按鈕元件展示了基本的事件處理:
onClick
屬性綁定點擊事件
handleClick
在元件內部定義
children
prop 接收按鈕文字
步驟 2:在 App 中使用按鈕元件
import { useState } from 'react'; |
這裡展示了兩種不同的按鈕:
<MyButton>
使用元件內部的事件處理
onClick={() => setCount(...)}
直接在 JSX 中定義
步驟 3:建立表單元件(preventDefault 應用)
import MyButton from '../MyButton/MyButton'; |
這個表單元件展示了:
preventDefault()
:阻止表單的預設提交行為(頁面重新載入)
onChange
事件:監聽輸入框內容變化
e.target.value
取得輸入值
步驟 4:整合表單到 App
//... |
通常建議將事件處理邏輯放在父元件(狀態所在處),子元件只負責觸發事件:
這種模式的優勢:
// 子元件:純展示 + 觸發事件 |
讓我們透過實際範例來學習狀態提升的概念。我們將修改
MyForm
元件,將事件處理邏輯提升到父元件
App
中。
步驟 1:修改 MyForm 元件(移除內部事件處理)
這個修改展示了:
handleSubmit
和
handelChangeText
onLokiSubmit
和
onLokiChange
接收父元件傳入的事件處理函式
import MyButton from '../MyButton/MyButton'; |
步驟 2:在 App 中實現事件處理邏輯
這個實作展示了:
MyForm
的事件處理邏輯移到
App
元件
onPasswordSubmit
),然後傳給子元件
onLokiSubmit={onPasswordSubmit}
將父元件的函式傳給子元件
//... |
狀態提升的優勢:
MyForm
可以在不同場景使用,只需傳入不同的事件處理函式
狀態提升的核心概念
在傳統的 JavaScript 中,我們可以直接操作 DOM 來更新畫面:
// 傳統 JavaScript 方式 |
但在 React 中,我們不能直接操作 DOM,因為 React 使用 虛擬 DOM(Virtual DOM) 來管理畫面更新。
React 的虛擬 DOM 是一個 JavaScript 物件,它代表真實 DOM 的結構:
// 虛擬 DOM 物件(簡化版) |
虛擬 DOM 的工作流程:
useState
的 setter 被調用時
為什麼需要虛擬 DOM?
傳統的 DOM 操作存在以下問題:
虛擬 DOM 的優勢:
效能對比範例:
// ❌ 傳統 DOM 操作(效能差) |
React 的虛擬 DOM 設計讓開發者可以專注於狀態管理,而不用擔心 DOM 操作的效能問題。
useState
是 React 提供的一個 Hook,用於在函式元件中管理狀態:
const [state, setState] = useState(initialValue); |
useState 的特點:
initialValue
是狀態的初始值
state
:當前的狀態值
setState
:更新狀態的函式
setState
被調用時,元件會重新渲染
基本使用範例:
import { useState } from 'react'; |
React 提供了兩種更新狀態的方式,理解它們的差異對於避免常見的狀態更新錯誤非常重要。
方式 1:直接傳遞新值
function Counter() {
|
方式 2:使用函式更新(推薦)
function Counter() {
|
為什麼推薦使用函式更新?
讓我們透過實際範例來理解兩種方式的差異:
問題場景:快速點擊按鈕
function ProblematicCounter() {
|
解決方案:使用函式更新
function FixedCounter() {
|
關鍵差異總結:
直接傳遞新值 當前渲染的狀態值 最新的狀態值 ❌ 可能使用舊值 ✅ 總是使用最新值 ❌ 可能計算錯誤 ✅ 正確處理 簡單的單次更新 複雜邏輯、多次更新最佳實踐建議:
讓我們透過建立一個圖片幻燈片元件來學習 useState 的實際應用。這個範例將展示多個狀態管理、條件渲染,以及狀態提升的概念。
步驟 1:準備圖片資料
const data = [ |
步驟 2:建立基礎幻燈片元件
import { useState } from 'react'; |
這個基礎版本展示了:
idx
來追蹤當前圖片
handlePrev
使用直接賦值,
handleNext
使用函式更新
步驟 3:添加多個狀態管理
一個元件可以多個 state 管理,我們多控制一個 boolean 是否要顯示 title。
import { useState } from 'react'; |
這個版本展示了:
idx
和
toShow
兩個獨立的狀態
{toShow && <label>}
來控制標題顯示
setToShow((bool) => !bool)
切換布林值
步驟 4:狀態提升到父元件
import { useState } from 'react'; |
import { useState } from 'react'; |
這個最終版本展示了:
toShow
狀態提升到
App
元件
MyGallery
元件共享同一個
toShow
狀態
{...{ toShow, setToShow }}
簡化 props 傳遞