WeHelp
React 是一款開源、高效、且市場佔有率最高的 JavaScript 前端框架,提供組件化開發和跨平台應用的能力。
  1. React 框架簡介
  2. Vite 專案管理
  3. 第一個 React 專案
  4. React 組件基礎
  5. JSX 樣板語法基礎
  6. JSX 樣板:行內樣式
  7. JSX 樣板:事件處理
  8. React 組件屬性
  9. React 組件狀態
  10. React 互動網頁範例
  11. React 表單元件開發
  12. React 組件狀態傳遞
  13. Hooks: useEffect
  14. Hooks: useRef
React 組件狀態傳遞
## React 如何管理多個組件間的狀態傳遞 React 的核心觀念是「**單向資料流(One-way Data Flow)**」: 1. 狀態通常由共同父組件管理。 2. 父組件透過 `props` 將資料傳給子組件。 3. 子組件不能直接修改父組件的狀態。 4. 子組件可以呼叫父組件傳入的 callback,通知父組件更新狀態。 5. 狀態更新後,React 重新渲染相關組件。 ### 常見做法 - **父傳子**:使用 `props` - **子傳父**:父組件傳入事件函式,例如 `onAdd` - **兄弟組件互傳**:將共享狀態提升到共同父組件,稱為 **Lifting State Up** - **跨多層組件傳遞**:可使用 `Context` - **大型應用程式**:可使用 Redux、Zustand、Jotai 等狀態管理工具 --- ## 整合範例:待辦事項 以下範例包含: - `App`:管理所有待辦事項狀態 - `TodoForm`:新增待辦事項 - `TodoList`:顯示待辦事項 - `TodoItem`:切換完成狀態或刪除 - `TodoSummary`:顯示統計資訊 ```jsx import { useState } from "react"; function App() { const [todos, setTodos] = useState([]); const [nextId, setNextId] = useState(1); function addTodo(title) { const newTodo = { id: nextId, title, completed: false, }; setTodos((currentTodos) => [...currentTodos, newTodo]); setNextId((id) => id + 1); } function toggleTodo(id) { setTodos((currentTodos) => currentTodos.map((todo) => todo.id === id ? { ...todo, completed: !todo.completed } : todo ) ); } function deleteTodo(id) { setTodos((currentTodos) => currentTodos.filter((todo) => todo.id !== id) ); } return ( <div> <h1>待辦事項</h1> <TodoForm onAdd={addTodo} /> <TodoSummary todos={todos} /> <TodoList todos={todos} onToggle={toggleTodo} onDelete={deleteTodo} /> </div> ); } function TodoForm({ onAdd }) { const [title, setTitle] = useState(""); function handleSubmit(event) { event.preventDefault(); const trimmedTitle = title.trim(); if (!trimmedTitle) return; onAdd(trimmedTitle); setTitle(""); } return ( <form onSubmit={handleSubmit}> <input value={title} onChange={(event) => setTitle(event.target.value)} placeholder="輸入待辦事項" /> <button type="submit">新增</button> </form> ); } function TodoList({ todos, onToggle, onDelete }) { if (todos.length === 0) { return <p>目前沒有待辦事項</p>; } return ( <ul> {todos.map((todo) => ( <TodoItem key={todo.id} todo={todo} onToggle={onToggle} onDelete={onDelete} /> ))} </ul> ); } function TodoItem({ todo, onToggle, onDelete }) { return ( <li> <label> <input type="checkbox" checked={todo.completed} onChange={() => onToggle(todo.id)} /> <span style={{ textDecoration: todo.completed ? "line-through" : "none", }} > {todo.title} </span> </label> <button onClick={() => onDelete(todo.id)}> 刪除 </button> </li> ); } function TodoSummary({ todos }) { const completedCount = todos.filter( (todo) => todo.completed ).length; return ( <p> 共 {todos.length} 項,已完成 {completedCount} 項 </p> ); } export default App; ``` ## 單向資料流分析 以上範例的資料流如下: ```text App ├─ todos ────────────────> TodoSummary └─ todos ────────────────> TodoList └─ todo ─────> TodoItem TodoForm ── onAdd(title) ──> App TodoItem ── onToggle(id) ──> App TodoItem ── onDelete(id) ──> App ``` ### 1. `App` 管理共享狀態 ```jsx const [todos, setTodos] = useState([]); ``` 因為 `TodoList`、`TodoSummary` 都需要使用 `todos`,所以狀態放在它們的共同父組件 `App` 中。 ### 2. 父組件將資料傳給子組件 ```jsx <TodoList todos={todos} /> <TodoSummary todos={todos} /> ``` 子組件透過 `props` 接收資料: ```jsx function TodoSummary({ todos }) { // 使用 todos } ``` ### 3. 子組件透過 callback 通知父組件 ```jsx <TodoForm onAdd={addTodo} /> ``` `TodoForm` 收到 `onAdd` 後,在表單送出時呼叫: ```jsx onAdd(trimmedTitle); ``` 真正修改 `todos` 的仍然是 `App`: ```jsx function addTodo(title) { setTodos((currentTodos) => [ ...currentTodos, { id: nextId, title, completed: false, }, ]); } ``` ### 4. 狀態更新後資料再次向下流動 當 `App` 更新 `todos` 後: ```text App 更新 todos ↓ TodoSummary 顯示新的統計 TodoList 顯示新的清單 TodoItem 顯示新的完成狀態 ``` 因此,React 中的狀態傳遞通常是: ```text 父組件:資料與狀態 ↓ props 子組件:顯示資料 ↑ callback 父組件:處理更新 ``` 這種方式能讓資料來源清楚、狀態容易追蹤,也能避免不同組件各自維護相同資料而造成不一致。
相關學習地圖、教學課程
F2E 網站前端工程
從 0 開始,成為網站前端工程師的學習路徑。