我的30天鐵人賽最後一天終於來了!!!
今天要來介紹的是To-Do List內的update更新功能,讓我們繼續看下去,這邊附上完整的程式碼連結在codesandbox
import React, { useState } from 'react'
import "./App.css"
const App = () => {
// 初始化我們input輸入框為空字串
const [inputToDo, setInputToDo] = useState("")
// 初始化我們待辦清單的所有內容為空陣列
const [toDoList, setToDoList] = useState([])
// 確認我們待辦事項的編輯狀態,初始Id為0
const [updateId, setUpdateId] = useState(0)
// 新增待辦事項的按鈕函數
const handleSubmit = (e) => {
// 當我們點選Add按紐時handleSubmit會被觸發
// 我們不希望它刷新頁面
e.preventDefault();
if (updateId) {
// 要用找到的id內的值回傳到輸入框並來編輯它
// 先確認跟我們要尋找的Id是否相符
// 它會詢問我們正在嘗試更新的待辦事項
// 如果"是"已更新的,我們提供原始的Id並執行Input寫入的任何內容
// 如果"不是",我們就提供它的默認值
const updateToDo = toDoList.find((i) => i.id === updateId);
const updatedToDoList = toDoList.map((t) => t.id === updateToDo.id
? (t = { id: t.id, inputToDo }) : {id: t.id, inputToDo: t.inputToDo}
);
setToDoList(updatedToDoList);
setUpdateId(0);
setInputToDo("");
return
}
// 只要inputToDo裡面不是空字串
// 就會創建新的To-Do List
if (inputToDo !== "") {
setToDoList([{ id: `${inputToDo}-${Date.now()}`, inputToDo }, ...toDoList])
// 輸入框新增按鈕送出後輸入框內的文字為空,記得下方input要寫value={inputToDo}
setInputToDo("");
}
};
// 控制delete按鈕的函式
// 將delete裡面的所有東西都做變量
// 設置filter過濾器去抓清單的獨特id
// 它將比較所有內容,如果與這個id匹配
// 那麼它就會被刪除
// 否則,如果不匹配這邊給不等於!==
// 它將不會被刪除
const handleDelete = (id) => {
const deleteToDo = toDoList.filter((to) => to.id !== id);
// 最後狀態需要更新回傳,將刪除傳遞給array
// ...為擴展運算符號
setToDoList([...deleteToDo]);
};
// 控制update按鈕的函式
// 需求為按下update按鈕後
// 更新前的文字會出現在輸入框內
// 我們做修改後送出會更新我們原本的To-Do List
// 用find找到我們單一的To-Do
// 讓它透過id去尋找我們要修改相同id的元素
// 它將回傳具有相同id的整個對象和To-Do內容在輸入框內
// 透過find尋找所有的To-Do list的array中的特定id相等就會回傳
// 接下來就是我們要修改的內容為setInputToDo
// updateTodo是一個object裡面包含一個id和一個To-Do
const handleUpdate = (id) => {
const updateToDo = toDoList.find((i) => i.id === id);
setInputToDo(updateToDo.inpuToDo);
setUpdateId(id);
};
return (
<div className="App">
<div className='container'>
{/* 標題 */}
<h1>To-Do List</h1>
{/* 待辦事項的輸入表單,提交按鈕觸發handleSubmit函式 */}
<form className='toDoForm' onSubmit={handleSubmit}>
{/* 新增待辦事項的輸入框,onChange每當輸入框改變時調用,value讓它等於{inputToDo},輸入新增按鈕後就會輸入框就會變成空 */}
<input type="text" value={inputToDo} onChange={(e) => setInputToDo(e.target.value)}/>
{/* 新增Add按鈕和修改Update的輸入框共用,UpdateId如果"有"返回"Update,否則返回Add*/}
<button type="sumbit">{updateId ? "Update" : "Add"}</button>
</form>
{/* 待辦清單顯示內容,t代表整個對象為輸入框內的inpuiToDo值 */}
<ul className='allToDoList'>
{toDoList.map((t) => (
<li className='singleTodoTask' >
<span className='toDoText' key={t.id}>{t.inputToDo}</span>
{/* 創建更新按鈕要傳入的函式 */}
<button type="button" onClick={() => handleUpdate(t.id)}>Update</button>
{/* 創建刪除按鈕要傳入的函式 */}
<button type="button" onClick={() => handleDelete(t.id)}>Delete</button>
</li>
))}
</ul>
</div>
</div>
)
}
export default App
後面補上React元件(Component)拆分檔案的教學,如何透過props從子層傳入父層,先來介紹它的概念。
Component 使你可以將 UI 拆分成獨立且可複用的程式碼,並且專注於各別程式碼的思考。
概念上來說,component 就像是 JavaScript 的 function,它接收任意的參數(稱之為「props」)並且回傳描述畫面的 React element。
Function Component 與 Class Component
定義 component 最簡單的方法即是撰寫一個 Javascript function:
function Greeting(props) {
return <h1>Hello, {props.name}</h1>;
}
此 function 是一個符合規範的 React component,因為它接受一個「props」(指屬性 properties)物件並回傳一個 React element。我們稱之為 function component,因為它本身就是一個 JavaScript function。
同樣的,也可以使用 ES6 Class 來定義 component:
class Greeting extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
上面兩種 component 在 React 中都是一樣的只是寫法不同。
這邊來介紹另外一個To-Do List的寫法,使用useState和useRef方法,並將元件內的檔案做拆分,完整程式碼在codesandbox這邊的連結
import { useRef, useState } from "react";
import ToDo from "./components/ToDo";
import "./styles.css";
// 原始To-Do資料結構
const todos = [
{
id: 1,
content: "do the dishes",
checked: true
}
];
export default function App() {
// 使用 useState 鉤子建立一個狀態變數 list 用來追蹤待辦事項的列表
// 初始值設定為 todos
const [list, setList] = useState(todos);
// isUpdated, setIsUpdated拆分到components > ToDo.js
// const [isUpdated, setIsUpdated] = useState(false);
// ref 的作法
// updateRef拆分到components > ToDo.js
// const updateRef = useRef(null);
// 建立一個 createdRef 使用 useRef 鉤子 將它設定為 null
// 將用於處理新待辦事項的輸入
const createdRef = useRef(null);
// onChecked 函數處理待辦事項的勾選狀態更新
const onChecked = (e) => {
const checkedTarget = list.find((el) => el.id === parseInt(e.target.id));
checkedTarget.checked = e.target.checked;
setList([...list]);
};
// onUpdate 函數處理待辦事項的內容更新
const onUpdate = (e) => {
const contentTarget = list.find((el) => el.id === parseInt(e.target.id));
contentTarget.content = e.target.value;
setList([...list]);
console.log(e.target.value);
};
// handleIsUpdated拆分到components > ToDo.js
// const handleIsUpdated = () => {
// setIsUpdated(!isUpdated);
// };
// confirmUpdated拆分到components > ToDo.js
// const confirmUpdated = () => {
// console.log(updateRef.current.value);
// const contentTarget = list.find(
// (el) => el.id === parseInt(updateRef.current.id)
// );
// contentTarget.content = updateRef.current.value;
// setList([...list]);
// };
// handleCreated 函數處理新建待辦事項的邏輯 並將其添加到列表中
const handleCreated = () => {
console.log(createdRef.current.value);
const newToDo = {
id: list[list.length - 1].id + 1,
content: createdRef.current.value,
checked: false
};
setList([...list, newToDo]);
};
// 在 return 中建立了一個包含 To-Do List 的 React 元件
// 包括輸入欄位、按鈕和待辦事項列表。
// 列表中的每個待辦事項都是使用 ToDo 組件來表示 並傳遞了必要的屬性。
return (
<div className="App">
<div className="container">
<h1>To-Do List</h1>
<div className="to_do_create">
<input className="inputText" type="text" ref={createdRef} />
<button type="button onClick={handleCreated}>Create</button>
</div>
<ol className="all_to_do_list">
{list.map((todo) => (
// 從子層ToDo.js中傳入修改及確認按鈕的元件到父層App.js
<ToDo key={todo.id} todo={todo} list={list} setList={setList} />
))}
</ol>
</div>
</div>
);
}
import { useRef, useState } from "react";
import "./ToDo.css";
// 定義ToDo組件,接受App.js的 todo、list 和 setList 作為屬性
const ToDo = ({ todo, list, setList }) => {
// 使用 useState 鉤子建立一個狀態變數 isUpdated 來追蹤是否處於待辦事項更新模式
const [isUpdated, setIsUpdated] = useState(false);
// ref 的作法
// 使用 useRef 鉤子建立一個 updateRef 它將用於更新待辦事項的內容
const updateRef = useRef(null);
// onChecked 函數處理待辦事項的勾選狀態更新,與 App.js 中的相同
const onChecked = (e) => {
const checkedTarget = list.find((el) => el.id === parseInt(e.target.id));
checkedTarget.checked = e.target.checked;
setList([...list]);
};
// handleIsUpdated 函數處理進入或退出待辦事項更新模式 切換 isUpdated 狀態
const handleIsUpdated = () => {
setIsUpdated(!isUpdated);
};
// confirmUpdated 函數處理確認待辦事項內容更新 同時將 isUpdated 狀態切換回原來的模式
const confirmUpdated = () => {
console.log(updateRef.current.value);
const contentTarget = list.find(
(el) => el.id === parseInt(updateRef.current.id)
);
contentTarget.content = updateRef.current.value;
setList([...list]);
handleIsUpdated();
};
// 在 return 中,根據 isUpdated 狀態來顯示待辦事項的文字或輸入欄位
// 以及更新和確認按鈕
return (
<li className="single_to_do_task" key={todo.id}>
<div className="to_do_text">
{isUpdated ? (
<input
id={todo.id}
// value={todo.content}
// onChange={onUpdate}
ref={updateRef}
/>
) : (
<p>{todo.content}</p>
)}
<input
id={todo.id}
type="checkbox"
checked={todo.checked}
onChange={onChecked}
/>
{!isUpdated && <button type="button" onClick={handleIsUpdated}>Update</button>}
{isUpdated && <button type="button" onClick={confirmUpdated}>Confirm</button>}
</div>
</li>
);
};
export default ToDo;
謝謝itHome的平台,敦促我完成這次三十天技術文章的鐵人賽挑戰,這是我第一次參加鐵人賽,原先只有初步的規劃,沒有特別的準備,幾乎都是當天完成上傳的,所以有點計畫趕不上變化,原本預計是想寫包含API串接的練習,這次最後只完成了前端畫面的新增、修改、刪除、查閱,前面主要都著重在轉職和學習資源的分享,也發現自己還有很多的不足需要再持續精進;這次也看到好多好棒的參賽文章,都說新的習慣養成需要二十一天,我之後也會盡量維持寫技術文章的習慣,一天進步一點點;期許自己下一次參加鐵人賽,一定要做好規劃,準備好主題和文章庫存,不然像這次遇到半路殺出的緊急狀況就差點無法應變了。
如果有錯誤歡迎指正,我會盡快修改。
前面多篇快速看過,認真看是從IA開始看,看著看著才發現這文章很有內容,尤其有提供大量的資料補充。讓我想到其實開發網站像我待的小公司這樣,一個專案從需求訪談到驗收都是同一個工程師,真的效率太慢。看到妳提供了大量工具跟循序又分工的開發流程,真的學了很多。
謝謝你的肯定我之前在開發過程中遇到許多困難,也是身邊的工程師朋友推薦給我it邦和谷哥大神們的文章學習,現在又多了一個學習好幫手就是Chat GPT!