# 資料流(Data Flow)——從入門到實務
## 單向資料流(Top‑down)為核心
* **資料只往下流**:父元件 → 子元件(透過 `props`)。
* **子元件不直接改 `props`**:要改資料,由**回呼**把意圖往上拋給父元件處理(父元件持有狀態並更新)。
* **優點**:可預測、可測試、思路清楚。
📌 **口訣**:**狀態靠近使用它的地方**(State Colocation)。需要多個子元件共享時,再把狀態往上「提升(Lifting State Up)」到它們的最近共同父層。
<br/>
## 父 → 子:以 `props` 傳入資料與行為
**Btn.jsx**(簡化 JS 版)
```jsx
const Btn = ({ color, onClick }) => (
<button
style={{ backgroundColor: color, padding: '20px', color: 'white' }}
onClick={onClick}
>
Click me {color}
</button>
);
export default Btn;
```
**App.js**
```jsx
import Btn from './components/Btn';
function App() {
return (
<div>
<Btn color="red" onClick={() => console.log('red')} />
<Btn color="blue" onClick={() => console.log('blue')} />
</div>
);
}
export default App;
```
> **重點**:`props` 可以傳**資料**(`color`)也可以傳**行為**(`onClick`)。

<br/>
## 子 → 父:回呼上拋(Callback to Parent)
當子元件需要「觸發修改」時,把事件透過回呼交給父層處理——**資料仍在父層更新**。
**TodoList / TodoItem(精簡示例)**
```jsx
// TodoItem.jsx
const TodoItem = ({ todo, onRemove }) => (
<article>
<h2>{todo.title}</h2>
<p>{todo.description}</p>
<button onClick={() => onRemove(todo._id)}>Remove</button>
</article>
);
export default TodoItem;
```
```jsx
// TodoList/index.jsx
import { useState, useCallback } from 'react';
import TodoItem from './TodoItem';
const TodoList = () => {
const [todoList, setTodoList] = useState([
{ _id: 1, title: 'Todo 1', description: 'Todo 1 description' },
{ _id: 2, title: 'Todo 2', description: 'Todo 2 description' },
]);
const removeTodo = useCallback((id) => {
setTodoList((list) => list.filter((t) => t._id !== id));
}, []);
return (
<article>
<h1>TodoList</h1>
<ul>
{todoList.map((todo) => (
<li key={todo._id}>
<TodoItem todo={todo} onRemove={removeTodo} />
</li>
))}
</ul>
</article>
);
};
export default TodoList;
```
> **最佳實務**:
>
> * `key` 用穩定 ID(`_id`),避免用 `index`。
> * 用 `useCallback` 穩定回呼參考,可搭配 `React.memo` 降低不必要重渲染。
<br/>
## 受控元件(Controlled Components)
把表單輸入綁到 state,**單一資料源**讓 UI 與資料永遠同步。
```jsx
const [keyword, setKeyword] = useState('');
<input value={keyword} onChange={(e) => setKeyword(e.target.value)} />
<ul>
{todoList
.filter((t) => t.title.toLowerCase().includes(keyword.toLowerCase()))
.map((t) => (
<li key={t.id}>{t.title}</li>
))}
</ul>
```
<br/>
## Context:避免層層傳遞(但別濫用)
**什麼時候用?** 當資料需要跨越多層傳遞(如 **目前使用者、主題、語系、權限**),就適合放 Context。
```jsx
import { createContext, useContext, useMemo, useState } from 'react';
const UserContext = createContext(null);
export const UserProvider = ({ children }) => {
const [user, setUser] = useState(null);
const value = useMemo(() => ({ user, setUser }), [user]);
return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
};
export const useUser = () => useContext(UserContext);
```
> **設計要點**:
>
> * 以 `useMemo` 穩定 `value`,降低 Provider 下所有子孫重渲染。
> * 多筆 Context 拆分(如 `ThemeContext`、`AuthContext`),**不要**把所有東西塞一個巨大的 Context。
> * 複雜讀取邏輯可用 **Selector Context**(為 `value` 提供切片),或改用外部狀態管理。
<br/>
## 伺服器資料 vs. 本地狀態:不同工具處理不同問題
* **本地狀態(UI 狀態)**:Modal 開關、輸入欄位值、目前頁碼… → `useState` / `useReducer` + Context。
* **伺服器狀態(Server State)**:來自 API 的可快取資料、同步/重新整理、背景更新、錯誤/載入狀態… → **React Query / SWR** 這類**資料抓取與快取工具**。
* **全域業務狀態(複雜讀寫、多人協作、可預測流程)**:用 **Redux Toolkit** 或 **Zustand/Jotai** 等狀態庫更好維護。
> **實務法則**:
>
> 1. 能就近放的 state,就近放(Colocation)。
> 2. 牽涉 API 快取與同步,優先考慮 React Query。
> 3. 需要跨許多頁面共享、流程複雜、可追蹤性要求高時,考慮全域狀態庫。
---
## 常見錯誤與避坑
* 直接改 state(例如 `state.list.push()`)→ 應回傳**新物件/新陣列**。
* 為了「以後可能會用到」就把所有 state 往上提 → 先靠近用處;真的需要共享再提升。
* Context 裝一切 → 造成整棵樹頻繁重渲染與耦合;拆 Context 或改用專門的狀態工具。
* `key` 用陣列索引 → 在新增/刪除時導致錯位與重用錯誤。
<br/>
## 範例:非同步載入 + 搜尋 + 刪除(整合版)
```jsx
import { useEffect, useMemo, useState, useCallback } from 'react';
import TodoItem from './TodoItem';
const TodoList = () => {
const [todoList, setTodoList] = useState([]);
const [keyword, setKeyword] = useState('');
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/todos?_limit=50')
.then((r) => r.json())
.then((data) => setTodoList(data));
}, []);
const deleteTodo = useCallback((id) => {
setTodoList((list) => list.filter((t) => t.id !== id));
}, []);
const filtered = useMemo(() => {
const k = keyword.trim().toLowerCase();
return k ? todoList.filter((t) => t.title.toLowerCase().includes(k)) : todoList;
}, [keyword, todoList]);
return (
<div>
<h1>Todo list</h1>
<input value={keyword} onChange={(e) => setKeyword(e.target.value)} placeholder="搜尋標題" />
<ul>
{filtered.map((todo, index) => (
<TodoItem key={todo.id} index={index} todo={todo} deleteTodo={deleteTodo} />
))}
</ul>
</div>
);
};
export default TodoList;
```
```jsx
// TodoItem.jsx
import { useState, memo } from 'react';
const TodoItem = memo(({ todo, index, deleteTodo }) => {
const { id, title, completed } = todo;
const [open, setOpen] = useState(false);
return (
<li>
<h2 onClick={() => setOpen((v) => !v)}>
{index + 1}. {title}
</h2>
{open && (
<>
<p>{completed ? '完成' : '未完成'}</p>
<button onClick={() => deleteTodo(id)}>Delete</button>
</>
)}
</li>
);
});
export default TodoItem;
```
[範例檔案](https://github.com/IffyArt/2025-fullstack-course-forntend/tree/feature/react-todolist)
<br/>
## 小結(速記版)
* **單向資料流**:父傳子用 `props`,子改資料用回呼上拋。
* **狀態放哪裡?** 一律先「就近放」,需要共享再提升;跨層再考慮 Context。
* **不同類型狀態用不同工具**:UI 狀態 → `useState/useReducer`;伺服器資料 → React Query;大型全域狀態 → Redux Toolkit / Zustand。
* **效能**:`key` 用 ID、用 `memo` 與 `useCallback`/`useMemo` 穩定參考。
> 這就是 React 資料流的全景地圖:**自下而上靠回呼、跨層靠 Context、跨頁跨模組靠狀態庫與資料抓取工具**。