前言 接下來這一章節我們將會實作一些小東西,主要是要整合前面的知識點,否則單看知識而沒有任何一點輸出的話是沒有任何用處的,因此就讓我們準備練習一下吧。
範本樣板 首先這邊我用 TailwindCSS 做了一個 ToDoList 範本,而這個範本主要會預先載入以下 CDN
因此會建議你往下看之前,可以先 Fork 回去到你自己的 CodePen 中,等一下你才方便實作,這裡面也包含了基本 HTML 樣板
ToDoList 範本
React ToDoList 接下來我們將會實作幾個功能
輸入代辦事項,點擊「+」後出現在下方
完成的代辦事項可以打勾完成
可顯示幾筆代辦事項
清空全部任務(不論完成與否都清除)
初始化 React 接下來就讓我們開始實作,首先先將基本的 React 建立出來,然後這邊可以先將 #root 裡面的元素先全部丟進去 React return 中,接著你必須修正幾個錯誤,否則會無法正常運作
第一個就是 JSX 本身是 JavaScript 擴充,因此 class 屬於 JavaScript 保留字,因此 class 要改成 className
第二個也就是 Expected corresponding JSX closing tag for <input>,這個錯誤簡單來講就是 JSX 比較嚴謹,因此要調整 input 補上結尾標籤(也就是補上 /)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 const App = ( ) => { return ( <div > <div className ="bg-indigo-500 p-5 h-screen" > <div className ="max-w-[768px] m-auto bg-white p-5" > <h1 className ="text-center text-2xl mb-4" > React ToDoList</h1 > <div className ="flex" > <input type ="text" className ="w-full rounded-l-lg border-l-2 border-y-2 border-indigo-300 pl-4 focus:outline-indigo-500 focus:outline-none focus:outline-offset-0" placeholder ="請輸入你的代辦事項" /> <button className ="w-[50px] h-[50px] border-0 bg-sky-500 hover:bg-sky-600 rounded-r-lg text-white transition duration-700" > +</button > </div > <ul > <li className ="py-4" > <label > <input type ="checkbox" /> 今天要洗碗 </label > </li > </ul > <div className ="flex justify-between items-center" > <p > 目前有 <span className ="font-medium" > 1</span > 個事項待完成 </p > <button type ="button" className ="bg-red-300 p-2 rounded-md hover:bg-red-400 transition duration-700" > Clear All Task</button > </div > </div > </div > </div > ) }const root = ReactDOM .createRoot (document .querySelector ('#root' )); root.render (<App /> );
如果你有依照以上步驟調整,你會發現 React 已經可以正常運作了,接著我們就可以開始實作功能了
宣告資料狀態 接下來要宣告一個初始資料狀態,是要拿來放 todo 新增的資料,這邊要注意的是初始資料會是一個陣列
1 const [ todoList, setTodoList ] = React .useState ([]);
點擊事件綁定 那麼接下來我們要先替幾個東西綁定事件,也就是點擊事件,第一個是「+」按鈕,而這個事件我們先對應到一個函式叫做 addTodo
1 <button onClick={ addTodo } className="w-[50px] h-[50px] border-0 bg-sky-500 hover:bg-sky-600 rounded-r-lg text-white transition duration-700" >+</button>
1 2 const addTodo = ( ) => { }
第二個則是 「Clear All Task」 按鈕,對應的是 remoteAllTodo
1 <button onClick={ remoteAllTodo } type="button" className="bg-red-300 p-2 rounded-md hover:bg-red-400 transition duration-700" >Clear All Todo </button>
1 2 const remoteAllTodo = ( ) => { }
第三個就是勾選 todo 狀態的 checkbox,但是這邊我們先不綁事件,先把上面這兩個綁好就好,等一下後面再來一起處理。
第一個我們先來寫新增代辦的函式,當我們觸發 onClick 事件之後,要去取得 input 的 value 因此要稍微調整一下 input 給予一個 id
1 <input id="todoInput" type="text" className="w-full rounded-l-lg border-l-2 border-y-2 border-indigo-300 pl-4 focus:outline-indigo-500 focus:outline-none focus:outline-offset-0" placeholder="請輸入你的代辦事項" />
接著改寫一下 addTodo 函式
1 2 3 4 const addTodo = (event ) => { const value = document .querySelector ('#todoInput' ).value ; console .log (value); };
接下來你在 input 隨便輸入並按下「+」 就可以看到我們正確取得 input 的值了。
當我們取得值之後,接下來就要將值推進去到 setTodoList 中,首先第一個會是解構原本資料的物件資料,第二個我們則要寫入另一個物件
1 2 3 4 5 6 7 8 9 const addTodo = ( ) => { const input = document .querySelector ('#todoInput' ); setTodoList ([ ...todoList, { name : input.value } ]) };
之所以要先解構原本資料是因為我們不想要直接覆蓋掉原本的資料,而是要將新的資料推進去,這樣才不會造成資料的遺失,因此才會需要先解構原本的資料,接著再推進去新的資料。
接下來我們也要寫入代辦新增的時間,時間最簡單方式就是使用 Date.now(),剛好這個 Date.now() 非常適合作為一個 Key 使用,因此就會直接將它作為一個 Key 使用以及最後補個這個代辦事項的狀態(已完成與未完成)
1 2 3 4 5 6 7 8 9 10 11 const addTodo = (event ) => { const input = document .querySelector ('#todoInput' ); setTodoList ([ ...todoList, { id : Date .now (), name : input.value , status : false , } ]) };
最後別忘記要清空 input 的欄位,畢竟新增成功就要清空
1 2 3 4 5 6 7 8 9 10 11 12 const addTodo = (event ) => { const input = document .querySelector ('#todoInput' ); setTodoList ([ ...todoList, { id : Date .now (), name : input.value , status : false , } ]) input.value = '' ; };
渲染代辦事項 接下來就是要渲染我們儲存 todoList 的代辦事項資料,如果是在 Vue 裡面我們會使用 v-for 來渲染畫面,而在 React 則是使用 map 並重新組合成一個 HTML 回傳給 JSX 來渲染資料
因此要將這一塊
1 2 3 4 5 6 7 8 <ul> <li className ="py-4" > <label > <input type ="checkbox" class ="mr-2" /> 今天要洗碗 </label > </li > </ul>
改成使用 map 組合 DOM,記得不要忘記 key 的存在,(記得補一個 checked)
1 2 3 4 5 6 7 8 9 10 11 12 <ul> { todoList.map ((todo ) => ( <li className ="py-4" key ={ todo.id }> <label > <input type ="checkbox" class ="mr-2" checked ={ todo.status }/> { todo.name } </label > </li > )) } </ul>
你可能會想說為什麼使用 map 就可以正常渲染資料?在前面章節我們有說過 JSX 本身會針對陣列展開 (Spread),而 map 會回傳一個陣列,因此就會正常渲染資料。
接著這邊你也可以順便把下方這一段改一下
1 2 3 <p> 目前有 <span className="font-medium" >1 </span> 個事項待完成 </p>
改成以下這樣就可以了
1 2 3 <p> 目前有 <span className="font-medium" >{ todoList.length }</span> 個事項待完成 </p>
基本上到了這一步驟之後,你就可以開始試著新增一個代辦事項,然後你也可以在下方看到資料被渲染出來囉。
checkbox 事件綁定 那麼接下來我們要增加當使用者勾選任務後,會補上一個刪除線的效果,如同底下
這是一條刪除線
而判斷方式非常簡單,由於我們是使用 TailwindCSS 來實作,因此在 className 上面補上一個判斷,當 state 為 true 的話,就會自動加上 line-through 樣式即可。
只是在開始之前我們要先針對 label 增加一個屬性,也就是 data-* 並傳入 key,這個我們稍後會使用到
1 2 3 4 5 6 7 8 9 10 11 12 <ul> { todoList.map ((todo ) => ( <li className ="py-4" key ={ todo.id }> <label className ={ todo.status ? 'line-through ' : ''}> <input type ="checkbox" data-id ={ todo.id } className ="mr-2" checked ={ todo.status }/> { todo.name } </label > </li > )) } </ul>
這時候你新增每一筆資料就都可以看到 Li 多了一個 data-id 的屬性。
接下來也要針對 label 綁定一個事件,也就是 onClick 事件,對應到 updateTodo 方法
1 2 3 4 5 6 7 8 9 10 11 12 <ul> { todoList.map ((todo ) => ( <li className ="py-4" key ={ todo.id } data-id ={ todo.id } > <label className ={ todo.status ? 'line-through ' : ''} > <input onClick ={ updateTodo } type ="checkbox" className ="mr-2" data-id ={ todo.id } checked ={ todo.status }/> { todo.name } </label > </li > )) } </ul>
而 updateTodo 會透過 event.target 去尋找 data-* 然後用這種方式去找出資料中符合 ID 的部分,並將該陣列資料轉換為 false or true
1 2 3 4 5 6 7 8 9 10 11 const updateTodo = (event ) => { const { id } = event.target .dataset ; const newTodoList = todoList.map ((todo ) => { if (todo.id === Number (id)) { todo.status = !todo.status ; } return todo; }); setTodoList ([ ...newTodoList ]); }
由於我們這邊解構出來的 dataset 都是一個字串,因此要記得補上 Number 做型別轉換。
接著這時候你可以嘗試輸入代辦事項並新增代辦,然後在打開 Console 應該會發現這個錯誤
Warning: You provided a checked prop to a form field without an onChange handler. This will render a read-only field. If the field should be mutable use defaultChecked. Otherwise, set either onChange or readOnly.
而這有幾種解決方式,將原本的 checked 改成 defaultChecked,或是在 input 上面增加一個 onChange 事件,這邊我們選擇後者。
1 2 3 4 5 6 7 8 9 10 11 12 <ul> { todoList.map ((todo ) => ( <li className ="py-4" key ={ todo.id } data-id ={ todo.id } > <label className ={ todo.status ? 'line-through ' : ''} > <input onChange ={ updateTodo } type ="checkbox" className ="mr-2" data-id ={ todo.id } checked ={ todo.status }/> { todo.name } </label > </li > )) } </ul>
這樣子就不會再出現該警告訊息了。
清空 Todo 恭喜你做到這邊的時候就已經將大部分功能給完成了!
所以最後就是單純的去撰寫 remoteAllTodo 裡面的功能,而這也其實非常簡單,因為我們只是要將代辦事項清空,因此只需要這樣寫即可
1 2 3 const remoteAllTodo = ( ) => { setTodoList ([]); };
非常簡單吧?你的第一個簡單版 React TodoList 就這樣完成了。
這邊也提供完整版 TodoList CodePen 的連結。
除此之外我也提供一版 Vue 版本的 TodoList CodePen 可以讓你比較一下兩者的差異。
後記 本文將會同步更新到我的部落格
整理這些技術筆記真的很花時間,如果你願意 關閉 Adblock 支持我,我會把這份感謝轉換成更多「踩坑轉避坑」的內容給你!ヽ(・∀・)ノ
Advertisement