DAY5(2025/06/18)
[TOC]
# DAY5(2025/06/18)React
## 進度日誌
* 開始 **API 串接 & useEffect 資料自動載入**
* 成功串接 JSONPlaceholder Todos API,自動顯示前 10 筆資料
* 熟悉 `fetch`/`useEffect` 非同步流程、TypeScript 型別、loading/error UI 處理
## 筆記區 📘 React
### ▶ API 串接 & useEffect 資料自動載入
* `fetch`(或 `axios`)從外部 API 拿資料
* `useEffect`:元件掛載時自動抓資料
* 資料型別安全處理(TypeScript 型別)
* 處理「載入中」、「錯誤」等 UI 狀態
---
#### 1️⃣ 小專案目標
串接「JSONPlaceholder」的 **Todos API**,自動顯示一串任務清單!
API 位置:[https://jsonplaceholder.typicode.com/todos](https://jsonplaceholder.typicode.com/todos)
---
##### 📦 基本流程
1. 建立一個新元件(或在 `App.tsx` 練習)
2. 用 `useEffect` 控制元件掛載時自動抓資料
3. 用 `useState` 管理「todos 資料」、「載入狀態」、「錯誤狀態」
4. 把資料顯示在畫面上
---
#### 2️⃣ 練習程式碼:`FetchTodos.tsx`
```tsx
import React, { useState, useEffect } from 'react';
// 定義 API 回傳的資料型別
type Todo = {
userId: number;
id: number;
title: string;
completed: boolean;
};
const FetchTodos: React.FC = () => {
const [todos, setTodos] = useState<Todo[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string>('');
useEffect(() => {
setLoading(true);
fetch('https://jsonplaceholder.typicode.com/todos')
.then(res => {
if (!res.ok) throw new Error('API 回傳失敗');
return res.json();
})
.then((data: Todo[]) => setTodos(data))
.catch(err => setError(err.message))
.finally(() => setLoading(false));
}, []);
if (loading) return <p>載入中...</p>;
if (error) return <p style={{ color: 'red' }}>錯誤:{error}</p>;
return (
<div>
<h2>API Todo List (前 10 筆)</h2>
<ul>
{todos.slice(0, 10).map(todo => (
<li key={todo.id}>
<input type="checkbox" checked={todo.completed} readOnly />
{todo.title}
</li>
))}
</ul>
</div>
);
};
export default FetchTodos;
```
---




---
#### 3️⃣ 在 `App.tsx` 引用
```tsx
import FetchTodos from './components/FetchTodos';
function App() {
return (
<div>
<h1>API 串接範例</h1>
<FetchTodos />
</div>
);
}
```
---
#### 成果
* `useEffect`:**元件初次渲染自動抓資料**
* `fetch` + `.then/.catch` 處理 API 資料流
* TypeScript 型別確保資料正確
* 三種 UI 狀態:Loading / Error / Success
* 只顯示前 10 筆:`.slice(0, 10)`
---
### ▶ API 資料結合 TodoList
#### 在 `App.tsx` 引用
```tsx
import React, { useState, useEffect } from 'react';
import Hello from './components/Hello';
import HelloWorldButton from './components/HelloWorldButton';
import ChildButton from './components/ChildButton';
import TodoItem from './components/TodoItem';
import TodoInput from './components/TodoInput';
import type { Todo } from './components/TodoItem';
function App() {
// ===== TodoList 狀態區 =====
const [todos, setTodos] = useState<Todo[]>([]);
const [loading, setLoading] = useState(false);
// Step 1: useEffect 抓 API 初始資料
useEffect(() => {
setLoading(true);
fetch('https://jsonplaceholder.typicode.com/todos')
.then(res => res.json())
.then((data: any[]) => setTodos(
data.slice(0, 10).map(todo => ({
id: todo.id,
text: todo.title,
done: todo.completed
}))
))
.finally(() => setLoading(false));
}, []);
// Step 2: 本地新增 Todo
const addTodo = (text: string) =>
setTodos([...todos, { id: Date.now(), text, done: false }]);
// Step 3: 切換完成
const toggleTodo = (id: number) =>
setTodos(todos.map(t => t.id === id ? { ...t, done: !t.done } : t));
// Step 4: 刪除 Todo
const deleteTodo = (id: number) =>
setTodos(todos.filter(t => t.id !== id));
// ===== 父子互動計數器組 =====
const [count, setCount] = useState<number>(0);
const handleChildClick = () => setCount(count + 1);
return (
<>
<div>
<h2>第一個元件</h2>
<Hello name="宸維(Shiki)" age={21} />
</div>
<br />
<div>
<h2>第二個元件</h2>
<HelloWorldButton />
</div>
<br />
<div>
<h2>第三個元件(父子元件互動練習)</h2>
<p>目前點擊次數:{count}</p>
<ChildButton onButtonClick={handleChildClick} />
</div>
<br />
<div>
<h2>Todo List (API 初始資料)</h2>
{loading
? <p>載入中...</p>
: (
<>
<TodoInput onAdd={addTodo} />
<ul>
{todos.map(t => (
<TodoItem
key={t.id}
todo={t}
onToggle={toggleTodo}
onDelete={deleteTodo}
/>
))}
</ul>
</>
)}
</div>
</>
);
}
export default App;
```

---