DAY4(2025/06/17)
[TOC]
# DAY4(2025/06/17)React
## 進度日誌
- 完成Todo List 元件拆分、完成/未完成切換
- 熟悉陣列 state 深度操作、callback 傳參數、條件樣式
## 筆記區📘 React
### ▶ Todo List 模組化 + 完成/未完成切換
---
#### 1️⃣ 建立資料夾與子元件
```
src/
└─ components/
├─ TodoItem.tsx
└─ TodoInput.tsx
````
---
#### 2️⃣ TodoItem.tsx
```tsx
import React from 'react';
export type Todo = { id: number; text: string; done: boolean };
type Props = {
todo: Todo;
onToggle: (id: number) => void;
onDelete: (id: number) => void;
};
const TodoItem: React.FC<Props> = ({ todo, onToggle, onDelete }) => (
<li>
<input
type="checkbox"
checked={todo.done}
onChange={() => onToggle(todo.id)}
/>
<span style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
{todo.text}
</span>
<button onClick={() => onDelete(todo.id)}>刪除</button>
</li>
);
export default TodoItem;
````
##### 1.`export type Todo = { id: number; text: string; done: boolean };`
意思:定義一個「Todo 型別」,用來描述每一個待辦事項長什麼樣子。
- `id: number` → 這個任務的「唯一編號」(用來分辨每一筆資料)
- `text: string` → 這個任務的「內容」文字
- `done: boolean` → 這個任務「完成了沒?」(true = 已完成,false = 未完成)
用途:以後你的每一個待辦事項都會長這個結構。
##### 2.`<input type="checkbox" checked={todo.done} onChange={() => onToggle(todo.id)} />`
這是一個勾選方塊,用來「切換完成/未完成」。
- `type="checkbox"`:讓這個 input 呈現為「勾選方塊」
- `checked={todo.done}`:這個勾選框「是否打勾」,根據這個任務的 `done` 屬性決定
- 如果 `todo.done` 是 `true`,就打勾
- 如果是 `false`,就沒打勾
- `onChange={() => onToggle(todo.id)}`:
- 當你「點擊這個勾選框」時,就會呼叫 `onToggle` 這個函式(父元件傳進來的),
- 並把這個任務的 id 當參數傳進去
- 用白話:**「當我勾/取消這個 checkbox,就通知父元件,把這一個任務的完成狀態切換」**
##### 3.`<span style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>{todo.text}</span>`
這一行是任務文字顯示,加上條件樣式:
- `style={{ textDecoration: todo.done ? 'line-through' : 'none' }}`
- 這是在「動態決定」這個任務的文字要不要「畫一條線」
- 如果 todo.done 是 true(已完成),就加上刪除線(line-through)
- 如果 todo.done 是 false(未完成),就正常顯示
- `{todo.text}`
- 這裡會顯示這個任務的內容
舉例:
- 如果這筆任務是「吃早餐」且 done: true,畫面會顯示「吃早餐」
- 如果 done: false,就顯示「吃早餐」沒有刪除線
---
#### 3️⃣ TodoInput.tsx
```tsx
import React, { useState } from 'react';
type Props = { onAdd: (text: string) => void };
const TodoInput: React.FC<Props> = ({ onAdd }) => {
const [value, setValue] = useState('');
const handleAdd = () => {
if (!value.trim()) return;
onAdd(value.trim());
setValue('');
};
return (
<>
<input value={value} onChange={e => setValue(e.target.value)} />
<button onClick={handleAdd}>新增</button>
</>
);
};
export default TodoInput;
```
---
#### 4️⃣ App.tsx 改寫
```tsx
import React, { useState } 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 addTodo = (text: string) =>
setTodos([...todos, { id: Date.now(), text, done: false }]);
const toggleTodo = (id: number) =>
setTodos(todos.map(t => (t.id === id ? { ...t, done: !t.done } : t)));
const deleteTodo = (id: number) =>
setTodos(todos.filter(t => t.id !== id));
//第三元件
const [count, setCount] = useState<number>(0); //預設Count = 0,setCount處理後續變更後count的值
const handleChildClick = () => {
setCount(count + 1);
}
return (
//<> ... </> 是 Fragment,不會多包一層 div,語法簡潔,React 專案很常見!
<>
<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</h2>
<TodoInput onAdd={addTodo} />
<ul>
{todos.map(t => (
<TodoItem
key={t.id}
todo={t}
onToggle={toggleTodo}
onDelete={deleteTodo}
/>
))}
</ul>
</div>
</>
);
}
export default App;
```
---