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; ``` ---