React 完全指南

React 是 Facebook 開發的宣告式、元件化的 JavaScript UI 函式庫,用於建構使用者介面。


目錄


什麼是 React?

React 是用於建構使用者介面的 JavaScript 函式庫,由 Facebook 於 2013 年開源。

核心特點

  • 🎯 宣告式:描述 UI 應該是什麼樣子,React 負責更新
  • 🧩 元件化:將 UI 拆分成獨立、可重用的元件
  • 📚 一次學習,到處使用:React Native、React VR 等
  • Virtual DOM:高效的 DOM 更新機制

為什麼選擇 React?

傳統 jQuery 方式:

// 命令式:告訴瀏覽器「怎麼做」
$('#button').click(function() {
  $('#counter').text(parseInt($('#counter').text()) + 1)
})

React 方式:

// 宣告式:描述「是什麼」
function Counter() {
  const [count, setCount] = useState(0)
  return <div>{count}</div>  // UI 自動更新
}

核心概念

1. Virtual DOM

使用者操作
更新 State
React 建立新的 Virtual DOM
與舊 Virtual DOM 比較(Diffing)
只更新變化的部分到真實 DOM

2. 單向資料流

Props(由上往下傳遞)
Parent Component
Child Component
Event(由下往上通知)

3. 元件思維

<App>
  <Header />
  <Main>
    <Sidebar />
    <Content>
      <Article />
      <Comments />
    </Content>
  </Main>
  <Footer />
</App>

快速開始

建立 React 專案

# 使用 Create React App
npx create-react-app my-app
cd my-app
npm start

# 使用 Vite(推薦,更快)
npm create vite@latest my-app -- --template react
cd my-app
npm install
npm run dev

第一個 React 應用

// src/App.jsx
import { useState } from 'react'

function App() {
  const [count, setCount] = useState(0)

  return (
    <div>
      <h1>Hello React!</h1>
      <p>計數:{count}</p>
      <button onClick={() => setCount(count + 1)}>
        點擊 +1
      </button>
    </div>
  )
}

export default App

JSX 語法

什麼是 JSX?

JSX = JavaScript + XML,是 JavaScript 的語法擴展。

// JSX
const element = <h1>Hello, World!</h1>

// 編譯後的 JavaScript
const element = React.createElement('h1', null, 'Hello, World!')

JSX 基本規則

1. 必須有一個根元素

// ❌ 錯誤:多個根元素
return (
  <h1>標題</h1>
  <p>內容</p>
)

// ✅ 正確:使用 Fragment
return (
  <>
    <h1>標題</h1>
    <p>內容</p>
  </>
)

// ✅ 正確:使用 div 包裹
return (
  <div>
    <h1>標題</h1>
    <p>內容</p>
  </div>
)

2. 使用 {} 嵌入 JavaScript 表達式

const name = 'Alice'
const age = 25

return (
  <div>
    <p>姓名:{name}</p>
    <p>年齡:{age}</p>
    <p>明年:{age + 1}</p>
    <p>{name.toUpperCase()}</p>
  </div>
)

3. className 而非 class

// ❌ 錯誤
<div class="container"></div>

// ✅ 正確
<div className="container"></div>

4. 駝峰式命名

// HTML 屬性用駝峰式
<div
  className="box"
  onClick={handleClick}
  onMouseEnter={handleHover}
  tabIndex={0}
/>

// CSS 屬性也用駝峰式
<div style={{
  backgroundColor: 'blue',
  fontSize: '16px'
}} />

5. 自閉合標籤必須加斜線

// ❌ 錯誤
<img src="logo.png">
<input type="text">

// ✅ 正確
<img src="logo.png" />
<input type="text" />

元件(Components)

函式元件(推薦)

// 基本函式元件
function Welcome() {
  return <h1>Hello!</h1>
}

// 箭頭函式元件
const Welcome = () => {
  return <h1>Hello!</h1>
}

// 簡寫(單行)
const Welcome = () => <h1>Hello!</h1>

類別元件(較舊的方式)

import React, { Component } from 'react'

class Welcome extends Component {
  render() {
    return <h1>Hello!</h1>
  }
}

元件命名規則

// ✅ 正確:大寫開頭
function UserProfile() { }
const ProductCard = () => { }

// ❌ 錯誤:小寫開頭(會被當作 HTML 標籤)
function userProfile() { }

Props 屬性

Props(Properties)是什麼?

  • Props 是父元件傳遞給子元件的資料
  • 類似函式的參數:父元件呼叫子元件時「傳入」的值
  • 特性:只能讀取,不能修改(唯讀)
父元件 → [傳遞 Props] → 子元件
        子元件使用 Props 顯示內容

傳遞 Props 的基本用法

// 父元件:傳遞資料給子元件
function App() {
  return (
    <UserCard
      name="Alice"           // 傳遞字串
      age={25}               // 傳遞數字(用 {} 包起來)
      isAdmin={true}         // 傳遞布林值
      hobbies={['coding', 'reading']}  // 傳遞陣列
    />
  )
}

// 子元件:接收 props 參數
function UserCard(props) {
  // props 是一個物件,包含所有傳入的屬性
  // props = {
  //   name: "Alice",
  //   age: 25,
  //   isAdmin: true,
  //   hobbies: ['coding', 'reading']
  // }

  return (
    <div>
      <h2>{props.name}</h2>
      <p>年齡:{props.age}</p>
      <p>管理員:{props.isAdmin ? '是' : '否'}</p>
      <ul>
        {props.hobbies.map((hobby, index) => (
          <li key={index}>{hobby}</li>
        ))}
      </ul>
    </div>
  )
}

傳遞不同類型的資料

<Component
  text="Hello"              // 字串:可以不用 {}
  number={42}               // 數字:必須用 {}
  boolean={true}            // 布林:必須用 {}
  array={[1, 2, 3]}        // 陣列:必須用 {}
  object={{ key: 'value' }} // 物件:必須用 {}
  function={handleClick}    // 函式:必須用 {}
/>

解構 Props(推薦寫法)

為什麼要解構?

  • 避免重複寫 props.
  • 程式碼更簡潔易讀
  • 一眼看出元件需要哪些資料
// ❌ 不解構:需要一直寫 props.xxx
function UserCard(props) {
  return (
    <div>
      <h2>{props.name}</h2>
      <p>年齡:{props.age}</p>
      <p>管理員:{props.isAdmin ? '是' : '否'}</p>
    </div>
  )
}

// ✅ 解構:直接使用變數名稱
function UserCard({ name, age, isAdmin }) {
  // 直接從 props 物件中取出需要的屬性
  return (
    <div>
      <h2>{name}</h2>          {/* 不用寫 props.name */}
      <p>年齡:{age}</p>       {/* 不用寫 props.age */}
      <p>管理員:{isAdmin ? '是' : '否'}</p>
    </div>
  )
}

解構的原理

// 這兩種寫法是等價的

// 方式 1:接收 props 物件
function UserCard(props) {
  const name = props.name
  const age = props.age
  // ...使用 name, age
}

// 方式 2:直接在參數中解構(推薦)
function UserCard({ name, age }) {
  // 直接使用 name, age
}

預設值(Default Props)

為什麼需要預設值?

  • 當父元件沒有傳遞某些 Props 時,使用預設值
  • 避免出現 undefined 錯誤
  • 讓元件更容易使用
// 使用 = 設定預設值
function Button({ text = '按鈕', color = 'blue', size = 'medium' }) {
  //            ^^^^^^^^^^^^   ^^^^^^^^^^^^^   ^^^^^^^^^^^^^^^
  //            如果沒傳 text  如果沒傳 color  如果沒傳 size
  //            就用這個預設值  就用這個預設值  就用這個預設值

  return (
    <button style={{ color, fontSize: size === 'large' ? '20px' : '16px' }}>
      {text}
    </button>
  )
}

// 使用範例
<Button />
// 結果:顯示「按鈕」,藍色,中等大小
// 因為所有屬性都使用預設值

<Button text="送出" />
// 結果:顯示「送出」,藍色(使用預設值),中等大小(使用預設值)

<Button text="刪除" color="red" />
// 結果:顯示「刪除」,紅色,中等大小(使用預設值)

<Button text="確認" color="green" size="large" />
// 結果:顯示「確認」,綠色,大字體
// 所有屬性都有傳入,不使用預設值

預設值的運作方式

// 當你這樣呼叫
<Button color="red" />

// 元件接收到的實際上是
function Button({ text = '按鈕', color = 'red', size = 'medium' }) {
  // text 沒傳 → 使用預設值 '按鈕'
  // color 有傳 'red' → 使用傳入的值 'red'
  // size 沒傳 → 使用預設值 'medium'
}

完整範例

// 定義元件
function Greeting({ name = '訪客', timeOfDay = '早安' }) {
  return <h1>{timeOfDay}{name}</h1>
}

// 使用方式和結果
<Greeting />
// 顯示:早安,訪客!

<Greeting name="Alice" />
// 顯示:早安,Alice!

<Greeting name="Bob" timeOfDay="晚安" />
// 顯示:晚安,Bob!

Props.children

// 父元件
function Card({ children }) {
  return (
    <div className="card">
      {children}
    </div>
  )
}

// 使用
<Card>
  <h1>標題</h1>
  <p>內容</p>
</Card>

Props 注意事項

// ✅ Props 是唯讀的
function Welcome(props) {
  // ❌ 不能修改 props
  props.name = 'New Name'  // 錯誤!

  // ✅ 可以基於 props 計算新值
  const greeting = `Hello, ${props.name}`
  return <h1>{greeting}</h1>
}

State 狀態

State 是什麼?

  • State = 元件的「記憶」或「內部狀態」
  • 是元件自己管理的可變資料
  • 當 State 改變時,React 會自動重新渲染元件,更新畫面

Props vs State 的差異

Props(屬性)              State(狀態)
-------------------       -------------------
從父元件傳入               元件自己管理
唯讀,不可修改             可以修改
類似函式的「參數」         類似變數的「值」
父元件控制                元件自己控制

範例:
Props: 使用者名稱         State: 計數器數值
Props: 按鈕文字           State: 表單輸入內容
Props: 圖片網址           State: 是否顯示下拉選單

useState Hook 基本用法

什麼是 Hook?

  • Hook 是 React 16.8 新增的特性
  • 讓函式元件也能使用 State 和其他 React 功能
  • useState 是最基本的 Hook,用來管理狀態
import { useState } from 'react'

function Counter() {
  // 宣告一個 state 變數
  const [count, setCount] = useState(0)
  //     ^^^^^  ^^^^^^^^           ^
  //     |      |                  初始值(第一次渲染時的值)
  //     |      更新函式(用來修改 count 的函式)
  //     當前狀態值(目前的數字)

  return (
    <div>
      <p>計數:{count}</p>
      <button onClick={() => setCount(count + 1)}>
        +1
      </button>
    </div>
  )
}

運作流程

1. 初始化:count = 0
2. 使用者點擊按鈕
3. 呼叫 setCount(count + 1) → count 變成 1
4. React 偵測到 state 改變
5. React 重新渲染元件
6. 畫面更新,顯示新的 count 值

useState 的完整解釋

const [count, setCount] = useState(0)
//    ^^^^^  ^^^^^^^^           ^^^
//    │      │                  └─ 初始值
//    │      └─ 更新函式(慣例命名:set + 狀態名稱)
//    └─ 狀態變數(可以取任何名字)

// 這是 JavaScript 的「陣列解構」語法
// 等同於:
const stateArray = useState(0)
const count = stateArray[0]      // 狀態值
const setCount = stateArray[1]   // 更新函式

實際範例

function LikeButton() {
  // 初始狀態:未按讚(false)
  const [liked, setLiked] = useState(false)

  return (
    <button onClick={() => setLiked(!liked)}>
      {liked ? '❤️ 已按讚' : '🤍 按讚'}
    </button>
  )
}

// 點擊流程:
// 1. liked = false → 顯示「🤍 按讚」
// 2. 點擊按鈕 → setLiked(!liked) → liked = true
// 3. React 重新渲染 → 顯示「❤️ 已按讚」
// 4. 再次點擊 → setLiked(!liked) → liked = false
// 5. 又顯示「🤍 按讚」

多個 State

function UserForm() {
  const [name, setName] = useState('')
  const [age, setAge] = useState(0)
  const [email, setEmail] = useState('')

  return (
    <form>
      <input
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <input
        value={age}
        onChange={(e) => setAge(e.target.value)}
      />
      <input
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
    </form>
  )
}

物件 State

function UserForm() {
  const [user, setUser] = useState({
    name: '',
    age: 0,
    email: ''
  })

  // 更新物件 state(必須使用展開運算子)
  const handleNameChange = (e) => {
    setUser({
      ...user,           // 保留其他屬性
      name: e.target.value  // 只更新 name
    })
  }

  return (
    <input
      value={user.name}
      onChange={handleNameChange}
    />
  )
}

陣列 State

function TodoList() {
  const [todos, setTodos] = useState([])

  // 新增項目
  const addTodo = (text) => {
    setTodos([...todos, { id: Date.now(), text }])
  }

  // 刪除項目
  const deleteTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id))
  }

  // 更新項目
  const updateTodo = (id, newText) => {
    setTodos(todos.map(todo =>
      todo.id === id ? { ...todo, text: newText } : todo
    ))
  }

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  )
}

State 更新是非同步的

function Counter() {
  const [count, setCount] = useState(0)

  const handleClick = () => {
    setCount(count + 1)
    console.log(count)  // ⚠️ 仍是舊值!
  }

  // ✅ 使用 useEffect 監聽變化
  useEffect(() => {
    console.log('count 已更新:', count)
  }, [count])
}

函式式更新

function Counter() {
  const [count, setCount] = useState(0)

  const handleClick = () => {
    // ❌ 可能有問題(基於舊值)
    setCount(count + 1)
    setCount(count + 1)  // 不會 +2

    // ✅ 正確方式(函式式更新)
    setCount(prevCount => prevCount + 1)
    setCount(prevCount => prevCount + 1)  // 會 +2
  }
}

Hooks 鉤子

Hooks 是什麼?

  • Hooks(鉤子) 是 React 16.8 引入的新功能
  • 函式元件也能使用 state 和其他 React 特性
  • 名稱都以 use 開頭(如 useState, useEffect
  • 必須在元件的最上層呼叫,不能在條件式或迴圈中使用

為什麼叫 "Hook"(鉤子)?

Hook = 讓你「鉤入」React 功能的函式

useState → 鉤入「狀態管理」功能
useEffect → 鉤入「副作用」功能
useContext → 鉤入「Context」功能

Hooks 使用規則

// ✅ 正確:在元件最上層使用
function MyComponent() {
  const [count, setCount] = useState(0)
  useEffect(() => { /* ... */ })
  return <div>{count}</div>
}

// ❌ 錯誤:在條件式中使用
function MyComponent() {
  if (condition) {
    const [count, setCount] = useState(0)  // 錯誤!
  }
}

// ❌ 錯誤:在迴圈中使用
function MyComponent() {
  for (let i = 0; i < 5; i++) {
    useEffect(() => { /* ... */ })  // 錯誤!
  }
}

useState - 狀態管理

用途:讓元件「記住」資料

語法

const [state, setState] = useState(initialValue)
//     ^^^^^  ^^^^^^^^           ^^^^^^^^^^^^
//     狀態值  更新函式            初始值

常見使用場景

// 1. 計數器
const [count, setCount] = useState(0)

// 2. 開關狀態
const [isOpen, setIsOpen] = useState(false)

// 3. 輸入框內容
const [text, setText] = useState('')

// 4. 選中的項目
const [selected, setSelected] = useState(null)

// 5. 列表資料
const [items, setItems] = useState([])

詳細說明:參考前面的 State 狀態 章節


useEffect - 副作用處理

副作用(Side Effect)是什麼?

  • 在 React 中,「副作用」指的是與渲染畫面無關的操作
  • 例如:資料獲取、訂閱、手動修改 DOM、計時器等

常見副作用

✓ 從 API 獲取資料
✓ 訂閱 WebSocket
✓ 設定計時器(setTimeout, setInterval)
✓ 手動修改 DOM(focus、scroll)
✓ 記錄日誌(console.log)
✓ 儲存到 localStorage

語法

useEffect(() => {
  // 副作用程式碼
  return () => {
    // 清理函式(cleanup)
  }
}, [dependencies])
//  ^^^^^^^^^^^^^^
//  依賴陣列:決定何時執行

useEffect 的三種模式

import { useEffect, useState } from 'react'

function Example() {
  const [count, setCount] = useState(0)

  // 模式 1:每次渲染後都執行
  useEffect(() => {
    console.log('元件已渲染')
  })
  // 沒有依賴陣列 → 每次渲染都執行

  // 模式 2:只在首次渲染執行
  useEffect(() => {
    console.log('元件已掛載(第一次渲染)')
  }, [])
  //  ^^ 空陣列 → 只執行一次

  // 模式 3:當特定值變化時執行
  useEffect(() => {
    console.log('count 已變更:', count)
  }, [count])
  //  ^^^^^^^ 依賴 count → count 變化時執行

  return <div>{count}</div>
}

依賴陣列的作用

沒有陣列        → 每次渲染都執行
useEffect(() => { ... })

空陣列 []       → 只在首次渲染執行(類似 componentDidMount)
useEffect(() => { ... }, [])

有值 [count]    → 當 count 變化時執行
useEffect(() => { ... }, [count])

多個值          → 當任何一個值變化時執行
useEffect(() => { ... }, [count, name, isOpen])

實際範例

function UserProfile({ userId }) {
  const [user, setUser] = useState(null)

  // 當 userId 改變時,重新獲取使用者資料
  useEffect(() => {
    console.log('獲取使用者資料:', userId)

    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => setUser(data))
  }, [userId])
  //  ^^^^^^^^ 依賴 userId

  // 執行時機:
  // 1. 第一次渲染:userId = 1 → 獲取 user 1 的資料
  // 2. userId 變成 2 → 再次執行,獲取 user 2 的資料
  // 3. userId 沒變 → 不執行
  // 4. userId 變成 3 → 再次執行,獲取 user 3 的資料

  return <div>{user?.name}</div>
}

清理函式(Cleanup Function)

為什麼需要清理?

  • 避免記憶體洩漏
  • 取消未完成的請求
  • 清除計時器
  • 取消訂閱
function Timer() {
  const [seconds, setSeconds] = useState(0)

  useEffect(() => {
    // 設定計時器
    const timer = setInterval(() => {
      setSeconds(s => s + 1)
    }, 1000)

    // 清理函式:元件卸載時執行
    return () => {
      clearInterval(timer)  // 清除計時器
      console.log('計時器已清除')
    }
  }, [])

  return <div>{seconds}</div>
}

// 執行順序:
// 1. 元件掛載 → 建立計時器
// 2. 元件卸載 → 執行清理函式 → 清除計時器

清理函式的執行時機

1. 元件卸載時(元件從畫面上移除)
2. 下次 effect 執行前(依賴改變時)
3. 確保舊的副作用被清理

範例:
useEffect(() => {
  console.log('訂閱', props.userId)
  return () => console.log('取消訂閱', props.userId)
}, [props.userId])

// props.userId = 1 → 訂閱 1
// props.userId = 2 → 取消訂閱 1 → 訂閱 2
// props.userId = 3 → 取消訂閱 2 → 訂閱 3
// 元件卸載 → 取消訂閱 3

useEffect 常見用途

// 1. 資料獲取
useEffect(() => {
  fetch('/api/users')
    .then(res => res.json())
    .then(data => setUsers(data))
}, [])

// 2. 訂閱
useEffect(() => {
  const subscription = props.source.subscribe()
  return () => subscription.unsubscribe()
}, [props.source])

// 3. 手動操作 DOM
useEffect(() => {
  document.title = `計數:${count}`
}, [count])

// 4. 監聽事件
useEffect(() => {
  const handleResize = () => {
    setWidth(window.innerWidth)
  }
  window.addEventListener('resize', handleResize)
  return () => window.removeEventListener('resize', handleResize)
}, [])

useContext - 跨元件傳遞資料

import { createContext, useContext } from 'react'

// 建立 Context
const UserContext = createContext()

// 父元件提供資料
function App() {
  const user = { name: 'Alice', age: 25 }

  return (
    <UserContext.Provider value={user}>
      <ChildComponent />
    </UserContext.Provider>
  )
}

// 子元件使用資料
function ChildComponent() {
  const user = useContext(UserContext)
  return <div>{user.name}</div>
}

useRef - 引用 DOM 元素

import { useRef } from 'react'

function TextInput() {
  const inputRef = useRef(null)

  const handleFocus = () => {
    inputRef.current.focus()
  }

  return (
    <>
      <input ref={inputRef} />
      <button onClick={handleFocus}>聚焦輸入框</button>
    </>
  )
}

useReducer - 複雜狀態管理

import { useReducer } from 'react'

// Reducer 函式
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 }
    case 'decrement':
      return { count: state.count - 1 }
    case 'reset':
      return { count: 0 }
    default:
      return state
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 })

  return (
    <div>
      <p>計數:{state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'reset' })}>重置</button>
    </div>
  )
}

useMemo - 效能優化(記憶計算結果)

import { useMemo } from 'react'

function ExpensiveComponent({ data }) {
  // 只在 data 變化時重新計算
  const processedData = useMemo(() => {
    console.log('處理資料中...')
    return data.map(item => item * 2)
  }, [data])

  return <div>{processedData}</div>
}

useCallback - 效能優化(記憶函式)

import { useCallback } from 'react'

function ParentComponent() {
  const [count, setCount] = useState(0)

  // 只在 count 變化時建立新函式
  const handleClick = useCallback(() => {
    console.log('clicked', count)
  }, [count])

  return <ChildComponent onClick={handleClick} />
}

自訂 Hook

// useLocalStorage.js
function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    const saved = localStorage.getItem(key)
    return saved ? JSON.parse(saved) : initialValue
  })

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value))
  }, [key, value])

  return [value, setValue]
}

// 使用
function App() {
  const [name, setName] = useLocalStorage('name', '')

  return (
    <input
      value={name}
      onChange={(e) => setName(e.target.value)}
    />
  )
}

事件處理

基本事件處理

function Button() {
  // 事件處理函式
  const handleClick = (e) => {
    e.preventDefault()  // 阻止預設行為
    console.log('按鈕被點擊')
  }

  return <button onClick={handleClick}>點我</button>
}

傳遞參數

function ItemList() {
  const handleDelete = (id) => {
    console.log('刪除項目:', id)
  }

  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>
          {item.name}
          {/* 方法一:箭頭函式 */}
          <button onClick={() => handleDelete(item.id)}>
            刪除
          </button>

          {/* 方法二:bind */}
          <button onClick={handleDelete.bind(null, item.id)}>
            刪除
          </button>
        </li>
      ))}
    </ul>
  )
}

常見事件

function EventExamples() {
  return (
    <div>
      {/* 滑鼠事件 */}
      <button onClick={handleClick}>點擊</button>
      <div onMouseEnter={handleHover}>懸停</div>
      <div onMouseLeave={handleLeave}>離開</div>

      {/* 鍵盤事件 */}
      <input onKeyDown={handleKeyDown} />
      <input onKeyUp={handleKeyUp} />
      <input onKeyPress={handleKeyPress} />

      {/* 表單事件 */}
      <input onChange={handleChange} />
      <form onSubmit={handleSubmit} />
      <input onFocus={handleFocus} />
      <input onBlur={handleBlur} />

      {/* 其他事件 */}
      <div onScroll={handleScroll} />
      <img onLoad={handleLoad} />
    </div>
  )
}

條件渲染

if/else 語句

function Greeting({ isLoggedIn }) {
  if (isLoggedIn) {
    return <h1>歡迎回來!</h1>
  } else {
    return <h1>請登入</h1>
  }
}

三元運算子

function Greeting({ isLoggedIn }) {
  return (
    <div>
      {isLoggedIn ? (
        <h1>歡迎回來!</h1>
      ) : (
        <h1>請登入</h1>
      )}
    </div>
  )
}

&& 邏輯運算子

function MessageBox({ hasMessage, message }) {
  return (
    <div>
      {hasMessage && <p>{message}</p>}
    </div>
  )
}

多條件渲染

function StatusBadge({ status }) {
  return (
    <div>
      {status === 'success' && <span className="badge-success">成功</span>}
      {status === 'error' && <span className="badge-error">錯誤</span>}
      {status === 'pending' && <span className="badge-pending">處理中</span>}
    </div>
  )
}

列表渲染

基本列表

function UserList({ users }) {
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}

Key 的重要性

// ❌ 錯誤:使用索引作為 key(可能導致問題)
{items.map((item, index) => (
  <li key={index}>{item}</li>
))}

// ✅ 正確:使用唯一 ID
{items.map(item => (
  <li key={item.id}>{item.name}</li>
))}

複雜列表

function ProductList({ products }) {
  return (
    <div className="products">
      {products.map(product => (
        <div key={product.id} className="product-card">
          <img src={product.image} alt={product.name} />
          <h3>{product.name}</h3>
          <p>{product.price}</p>
          <button>加入購物車</button>
        </div>
      ))}
    </div>
  )
}

表單處理

受控元件

function LoginForm() {
  const [username, setUsername] = useState('')
  const [password, setPassword] = useState('')

  const handleSubmit = (e) => {
    e.preventDefault()
    console.log('登入:', username, password)
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={username}
        onChange={(e) => setUsername(e.target.value)}
        placeholder="使用者名稱"
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="密碼"
      />
      <button type="submit">登入</button>
    </form>
  )
}

多個輸入欄位

function UserForm() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    age: ''
  })

  const handleChange = (e) => {
    const { name, value } = e.target
    setFormData({
      ...formData,
      [name]: value
    })
  }

  return (
    <form>
      <input
        name="name"
        value={formData.name}
        onChange={handleChange}
      />
      <input
        name="email"
        value={formData.email}
        onChange={handleChange}
      />
      <input
        name="age"
        value={formData.age}
        onChange={handleChange}
      />
    </form>
  )
}

Checkbox 和 Radio

function PreferencesForm() {
  const [newsletter, setNewsletter] = useState(false)
  const [theme, setTheme] = useState('light')

  return (
    <form>
      {/* Checkbox */}
      <label>
        <input
          type="checkbox"
          checked={newsletter}
          onChange={(e) => setNewsletter(e.target.checked)}
        />
        訂閱電子報
      </label>

      {/* Radio */}
      <div>
        <label>
          <input
            type="radio"
            value="light"
            checked={theme === 'light'}
            onChange={(e) => setTheme(e.target.value)}
          />
          淺色主題
        </label>
        <label>
          <input
            type="radio"
            value="dark"
            checked={theme === 'dark'}
            onChange={(e) => setTheme(e.target.value)}
          />
          深色主題
        </label>
      </div>
    </form>
  )
}

生命週期

函式元件生命週期(使用 useEffect)

function LifecycleDemo() {
  const [count, setCount] = useState(0)

  // 1. 元件掛載(componentDidMount)
  useEffect(() => {
    console.log('元件已掛載')
  }, [])

  // 2. 元件更新(componentDidUpdate)
  useEffect(() => {
    console.log('count 已更新:', count)
  }, [count])

  // 3. 元件卸載(componentWillUnmount)
  useEffect(() => {
    return () => {
      console.log('元件即將卸載')
    }
  }, [])

  // 4. 每次渲染後
  useEffect(() => {
    console.log('元件已渲染')
  })
}

完整生命週期範例

function DataFetcher({ userId }) {
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    // 掛載或 userId 變化時執行
    setLoading(true)

    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => {
        setData(data)
        setLoading(false)
      })

    // 清理函式(卸載或下次執行前)
    return () => {
      console.log('清理舊的請求')
    }
  }, [userId])

  if (loading) return <div>載入中...</div>
  return <div>{data.name}</div>
}

Context API

建立和使用 Context

import { createContext, useContext, useState } from 'react'

// 1. 建立 Context
const ThemeContext = createContext()

// 2. 建立 Provider 元件
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light')

  const toggleTheme = () => {
    setTheme(theme === 'light' ? 'dark' : 'light')
  }

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  )
}

// 3. 建立自訂 Hook
function useTheme() {
  const context = useContext(ThemeContext)
  if (!context) {
    throw new Error('useTheme 必須在 ThemeProvider 內使用')
  }
  return context
}

// 4. 在應用中使用
function App() {
  return (
    <ThemeProvider>
      <Header />
      <Main />
    </ThemeProvider>
  )
}

function Header() {
  const { theme, toggleTheme } = useTheme()

  return (
    <header className={theme}>
      <button onClick={toggleTheme}>切換主題</button>
    </header>
  )
}

效能優化

React.memo - 防止不必要的重渲染

import { memo } from 'react'

// 只在 props 變化時重新渲染
const ExpensiveComponent = memo(function ExpensiveComponent({ data }) {
  console.log('重新渲染')
  return <div>{data}</div>
})

// 自訂比較函式
const ExpensiveComponent = memo(
  function ExpensiveComponent({ data }) {
    return <div>{data}</div>
  },
  (prevProps, nextProps) => {
    // 回傳 true 表示不重渲染
    return prevProps.data === nextProps.data
  }
)

useMemo - 記憶計算結果

function DataProcessor({ data }) {
  // 只在 data 變化時重新計算
  const processedData = useMemo(() => {
    console.log('處理資料...')
    return data.map(item => item * 2)
  }, [data])

  return <div>{processedData}</div>
}

useCallback - 記憶函式

function ParentComponent() {
  const [count, setCount] = useState(0)

  // 避免每次渲染都建立新函式
  const handleClick = useCallback(() => {
    console.log('clicked')
  }, [])

  return <ChildComponent onClick={handleClick} />
}

const ChildComponent = memo(({ onClick }) => {
  console.log('子元件渲染')
  return <button onClick={onClick}>點我</button>
})

程式碼分割(Code Splitting)

import { lazy, Suspense } from 'react'

// 動態引入元件
const HeavyComponent = lazy(() => import('./HeavyComponent'))

function App() {
  return (
    <Suspense fallback={<div>載入中...</div>}>
      <HeavyComponent />
    </Suspense>
  )
}

常用第三方庫

React Router - 路由管理

npm install react-router-dom
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom'

function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">首頁</Link>
        <Link to="/about">關於</Link>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/users/:id" element={<UserDetail />} />
      </Routes>
    </BrowserRouter>
  )
}

Axios - HTTP 請求

npm install axios
import axios from 'axios'
import { useEffect, useState } from 'react'

function UserList() {
  const [users, setUsers] = useState([])

  useEffect(() => {
    axios.get('/api/users')
      .then(response => setUsers(response.data))
      .catch(error => console.error(error))
  }, [])

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}

React Query - 資料獲取

npm install @tanstack/react-query
import { useQuery } from '@tanstack/react-query'

function Users() {
  const { data, isLoading, error } = useQuery({
    queryKey: ['users'],
    queryFn: () => fetch('/api/users').then(res => res.json())
  })

  if (isLoading) return <div>載入中...</div>
  if (error) return <div>錯誤:{error.message}</div>

  return (
    <ul>
      {data.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}

Zustand - 狀態管理

npm install zustand
import create from 'zustand'

// 建立 store
const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 }))
}))

// 使用 store
function Counter() {
  const { count, increment, decrement } = useStore()

  return (
    <div>
      <p>{count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  )
}

最佳實踐

1. 元件設計原則

// ✅ 單一職責:每個元件只做一件事
function UserAvatar({ url, size }) { }
function UserName({ name }) { }
function UserCard({ user }) {
  return (
    <>
      <UserAvatar url={user.avatar} size="large" />
      <UserName name={user.name} />
    </>
  )
}

// ❌ 避免:一個元件做太多事
function UserEverything() {
  // 處理使用者、訂單、評論...
}

2. Props 驗證(使用 TypeScript)

interface ButtonProps {
  text: string
  onClick: () => void
  variant?: 'primary' | 'secondary'
}

function Button({ text, onClick, variant = 'primary' }: ButtonProps) {
  return <button onClick={onClick}>{text}</button>
}

3. 避免在渲染中建立函式

// ❌ 每次渲染都建立新函式
function Parent() {
  return <Child onClick={() => console.log('click')} />
}

// ✅ 使用 useCallback
function Parent() {
  const handleClick = useCallback(() => {
    console.log('click')
  }, [])

  return <Child onClick={handleClick} />
}

4. 合理拆分元件

// ✅ 好的拆分
function ProductCard({ product }) {
  return (
    <div>
      <ProductImage image={product.image} />
      <ProductInfo name={product.name} price={product.price} />
      <ProductActions productId={product.id} />
    </div>
  )
}

// ❌ 過度拆分(每個 div 都是元件)

5. 使用解構和預設值

// ✅ 清晰易讀
function Button({
  text,
  onClick,
  variant = 'primary',
  disabled = false
}) {
  return <button onClick={onClick}>{text}</button>
}

// ❌ 難以維護
function Button(props) {
  return <button onClick={props.onClick}>{props.text}</button>
}

6. 條件渲染技巧

// ✅ 早期返回
function UserProfile({ user }) {
  if (!user) return <div>請登入</div>
  if (user.banned) return <div>帳號已被封鎖</div>

  return <div>{user.name}</div>
}

// ❌ 巢狀的條件
function UserProfile({ user }) {
  return (
    <div>
      {user ? (
        user.banned ? (
          <div>帳號已被封鎖</div>
        ) : (
          <div>{user.name}</div>
        )
      ) : (
        <div>請登入</div>
      )}
    </div>
  )
}

7. 自訂 Hook 封裝邏輯

// ✅ 封裝可重用邏輯
function useFetch(url) {
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(data => {
        setData(data)
        setLoading(false)
      })
  }, [url])

  return { data, loading }
}

// 使用
function Users() {
  const { data, loading } = useFetch('/api/users')
  if (loading) return <div>載入中...</div>
  return <div>{data}</div>
}

總結

React 核心概念

React = 宣告式 + 元件化 + 單向資料流

元件:UI 的積木
Props:元件的輸入(唯讀)
State:元件的記憶(可變)
Hooks:函式元件的超能力

學習路徑

1. JSX 語法 ✓
2. 元件和 Props ✓
3. State 和事件處理 ✓
4. 列表和條件渲染 ✓
5. Hooks(useState, useEffect)✓
6. 表單處理 ✓
7. 效能優化 ✓
8. Router 和狀態管理
9. TypeScript
10. 測試

快速參考

// 元件
function Component() { return <div>Hello</div> }

// State
const [value, setValue] = useState(0)

// Effect
useEffect(() => { /* 副作用 */ }, [依賴])

// 事件
<button onClick={handleClick}>

// 列表
{items.map(item => <li key={item.id}>{item}</li>)}

// 條件
{condition && <div>顯示</div>}
{condition ? <A /> : <B />}

參考資源



建立日期:2024-11-14 最後更新:2025-11-19(改善 Props、State、Hooks 說明) 適用版本:React 18.x

🔗相關文章