用React 來做一個todolist吧!
不管是學哪一種框架,todolist可以說是經典練習題,此次練習除了實現基本的功能新增與刪除之外,再加一個拖曳改變排序功能
- 新增 :當使用者輸入完要新增的代辦項目後並按下enter後 ,就會觸發新增的function ,並且檢查輸入值是否為空,若為無效的值會跳alert提醒使用者
- 刪除 : 按下刪除時 ,參數帶入該項目的index ,並利用splice移除
- 拖曳排序 : 拖曳排序功能利用HTML5 Drag & Drop API 來實現,在拖曳起始的時候會先將拖曳對象的index存在BeginIndex,在滑過其他項目時觸發dispatch ,調整當前排序,當拖曳結束後清除BeginIndex
雖然用到redux來處理todolist有點殺雞焉用牛刀,但就是拿來練習看看!
本來以為很快就可以搞定一切,殊不知遇到一個很神秘的問題, 我刪除了第一個項目,但居然一口氣被刪了兩個,其實不只是刪除 ,連新增也是會執行兩次,查看觸發的function只有執行一遍,但是reducer的部分居然執行了兩次?為什麼?
透過估狗查到的解法是,將reducer獨立出來,不要放在原本的function component裡面,因為reducer的instance會隨著function component變化,將reducer抽離出來之後,恩..完全沒有反應,reducer還是觸發兩遍
最後的解決方法是移除React.StrictMode,如果用了StrictMode嚴格模式,那麼redux就會被多次的觸發,確保運作正常
There is no “problem”. React intentionally calls your reducer twice to make any unexpected side effects more apparent. Since your reducer is pure, calling it twice doesn’t affect the logic of your application. So you shouldn’t worry about this.
In production, it will only be called once.
不過我不確定這是否為正規的做法,但為了讓程式可以正常運作,只能先摘掉StrictMode,如果有更好的做法歡迎告知
完整程式碼如下
import React,{useState, Fragment, useReducer } from 'react';const initalState = {list: ["shopping", "sleep"]};const reducer = (state, action) => {console.log('action',action)switch (action.type) {case "ADD_ITEM": {return {list: action.oldList};}case "DELETE_ITEM": {state.list.splice(action.index, 1);console.log('oldold',state.list)return {list: state.list};}case "EXCHANGE_ITEM": {const fromIndex = action.fromIndex;const toIndex = action.toIndex;console.log(action.fromIndex, action.toIndex);console.log('before',state.list);const [del] = state.list.splice(fromIndex, 1);state.list.splice(toIndex, 0, del);console.log('after',state.list)return {list: state.list};}default: {return state;}}};const useListReducer = () =>{return useReducer(reducer, initalState);}const ToDoList = () =>{const [state, dispatch] = useListReducer();const [inputValue, setInputValue] = useState('')const [BeginIndex, setaBeginIndex] = useState(0);const deleteItem = (index) =>{console.log('deleteIndex',index)dispatch({type: "DELETE_ITEM",index});}const addItem = e =>{const value = e.target.valueif(e.key==='Enter'){if(!value) alert('請輸入代辦項目')let oldList = [...state.list]oldList.push(value)dispatch({type: "ADD_ITEM",oldList});setInputValue('')}}// const [state, dispatch] = useReducer(reducer, initalState);const handleChange = e => {setInputValue(e.target.value);};return(<Fragment><h3>新增代辦項目</h3><input value={inputValue} onChange={handleChange} onKeyDown={addItem} /><ul>{state.list.map((item,i)=>{return(<oldraggable='true'onDragStart={()=>{setaBeginIndex(i)}}onDragEnter={() =>{dispatch({type: "EXCHANGE_ITEM",fromIndex: BeginIndex,toIndex: i});setaBeginIndex(i)}}onDragEnd={()=>{setaBeginIndex(undefined)}}key={i}><span>{item}</span><button onClick={()=>{deleteItem(i)}}>delete</button></ol>)})}</ul></Fragment>)}export default ToDoList
參考資料