iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 28
4
Modern Web

一步一腳印的React旅程系列 第 28

[筆記][React]來做個作品吧!待辦事項「todolist」篇(8)-人生不能重來,但資料可以修改

嗨囉!大家好!最後三天最難熬!昨天已經能夠展開我們的編輯區了,今天來把編輯資料的部分做完吧!


那一開始先來做Redux的部分吧!要處理的事情有以下幾點,讓我們邊說邊做:

  1. 首先要在constants/todoAction-type.js中新增一個指令並匯出:
    export const EDIT_TODOLIST = "EDIT_TODOLIST"
    
  2. actions/todoList.js用剛剛新增的指令建構事件:
    import {EDIT_TODOLIST} from "../constants/todoAction-type.js"
    
    export const editTodoList = todoList => ({
        type : EDIT_TODOLIST,
        payload : todoList
    })
    
  3. 三部曲的最後到reducers/todoList.js中寫下該事件要怎麼處理資料(以下就只描述EDIT_TODOLIST的部分,之前寫過的就不另外寫了):
    //先匯入指令
    import { EDIT_TODOLIST } from "../constants/todoAction-type.js"
    
    const todoListReducer = (state = todoData, action) => {
        switch (action.type) {
            case EDIT_TODOLIST: {
                //先以目前的資料去複製另一個全新的陣列
                let newState = state.slice(0)
                //下迴圈比對id值
                for (let i = 0; i <= newState.length - 1; i++) {
                    if (newState[i].id === action.payload.id){
                        //將新的資料用splice()取代原本的位置中的資料
                        newState.splice(i, 1, action.payload)
                        break;
                    }
                }
                //回傳處理後的新資料
                return newState
            }
        }
    }
    

處理完Redux後就是React的問題了,記得我們昨天在List.jsx中有寫下當在外面點擊完成或是標記重要時會觸發changeState事件,來更改組件中的state嗎?但昨天那樣子做也只是做做樣子,更新state而已,其實在store的資料根本沒變,所以接著把剛剛寫的editTodoList放到裡面去。

  1. 先將editTodoList事件匯入List.jsx中:

    import { editTodoList } from "../../actions"
    
  2. 需要用到store的事件內容,所以原本的List組件要經過connect,以下把它改名成ConnectList,經過connect後再指定給List

    class ConnectList extends React.Component {
        {/*中間省略*/}
    }
    
  3. connect需要的參數mapDispatchToProps建構出來:

    const mapDispatchToProps = dispatch => {
        return {
            editTodoList: todoList => dispatch(editTodoList(todoList))
        }
    }
    
  4. 接著把List組件和mapDispatchToPropsconnect

    //記得匯入connect
    import { connect } from "react-redux"
    
    //因為只用到事件沒用到資料,所以第一個參數給null
    const List = connect(null, mapDispatchToProps)(ConnectList)
    
  5. List組件在經過connect後就能夠透過this.props.editTodoList來執行更新,那我們要在標記完成或重要時去執行,也就是在changeState裡面多執行一段送到redux更新的程式,以下先在ConnectList中另外寫一段function

    class ConnectList extends React.Component {
        constructor(props) {
            //其餘省略
    
            //在constructor中指定執行時的this
            this.updateTodolist = this.updateTodolist.bind(this)
    
            //其餘省略
        }
    
        //其餘省略
    
        updateTodolist(){
            //複製一份新的物件,為該代辦事項的資料
            let updateList = Object.assign({},this.props.listData)
            //用之前學過的解構賦值把complete和important兩個欄位替換成state的值
            updateList = {...updateList
                            ,complete:this.state.complete
                            ,important:this.state.important}
            //透過editTodoList丟到redux更新
            this.props.editTodoList(updateList)
        }
    
        //其餘省略
    }
    

    最後把該updateTodolist放到changeStatesetState的第二個參數,記得不要直接放在下一行程式執行,因為會有執行緒的問題:

    changeState(type) {
        //以下setState的第二個參數都是更新的函式
        switch (type) {
            case "complete": {
                this.setState({ complete: window.event.target.checked }
                                ,this.updateTodolist)
                break;
            }
            case "important": {
                if (this.state.important === '')
                    this.setState({ important: 'Y' },this.updateTodolist)
                else
                    this.setState({ important: '' },this.updateTodolist)
                break;
            }
        }
    }
    

經過上方的調整,就可以先來看看外層List在標記已完成或是否重要後的資料變化:
修改前:
https://ithelp.ithome.com.tw/upload/images/20181028/201069351xEC0FNrMH.png

修改後:
https://ithelp.ithome.com.tw/upload/images/20181028/20106935wWa8k4qnQM.png

外面的List處理完後要來處理裡面表單的部分了,這邊比較特別的是,因為在編輯時也可以點選已完成或標記重要,但使用者在做這個動作的時候應該不需要點選+ SAVE就能改變狀態,所以這裡我的處理方式先將在List組件中的changeState事件傳入InputTask中,所以在openEdit中的setState改成下方這樣子(這裡也一併將修改資料的editTodoList也傳進去,之後再說明這部分):

this.setState({editTasks:(<InputTask closeAdd={this.closeEdit}
                listData={this.props.listData}
                changeState={this.changeState.bind(this)}
                editTodoList={this.props.editTodoList} />)})

List組件的changeState事件是針對標記重要和完成用的,所以在InputTask中更改這兩個欄位的tagImportantchangeState都要在另外觸發this.props.changeState事件,將資料同步更新到外面List組件的state,同時又會觸發生命週期的componentDidUpdate()去更新store的資料。

而上面的做法也要考慮到在新增時是不會有this.props.changeState傳入的,所以在constructor中先去設定該事件,如果是新增就不要執行,不然會出錯:

class ConnectInputTask extends React.Component {
    constructor(props) {
        //其餘省略
        //如果有該事件就執行,沒有代表是新增狀態,並重新命名為changeListState
        this.changeListState = type =>{
        if(this.props.changeState)
            this.props.changeState(type)
        else
            console.log('新增狀態所以沒有this.props.changeState')
        }
        //其餘省略
    }
    //以下省略
}

指定完事件就能在完成和重要這兩個資料改變時觸發他了:

tagImportant() {
    if (this.state.important === '') {
        this.setState({ important: 'Y' })
    }
    else {
        this.setState({ important: '' })
    }
    //一併更新狀態到外面的`List`組件去
    this.changeListState('important')
}

changeState(event) {
    let value = event.target.value
    if (event.target.name === 'file') {
        value = value.substring(value.lastIndexOf('\\') + 1)
    }
    else if (event.target.name === 'complete') {
        value = event.target.checked
        //一併更新狀態到外面的`List`組件去
        this.changeListState('complete')
    }
    this.setState({ [event.target.name]: value })
}

來看看結果吧!當資料在編輯表單時點選已完成和標記重要:
修改前:
https://ithelp.ithome.com.tw/upload/images/20181028/20106935FtrlmqgWuO.png

修改後:
https://ithelp.ithome.com.tw/upload/images/20181028/20106935M3ZcMokfDO.png

也來看看新增時標記:
https://ithelp.ithome.com.tw/upload/images/20181028/20106935gveNrV0ZCw.png

都好後剩下的部分最簡單,InputTasksForm組件中的+ SAVE是由InputTask組件將submitTodo傳入做新增的,現在只要調整submitTodo,讓他可以再新增時執行新增,編輯時執行剛剛從Listprops傳進來的editTodoList事件就好了!

這裡我用id簡單判斷,因為如果是新增就沒id,編輯的話資料本身就會有id,所以submitTodo改成這樣子判斷:

submitTodo() {
    if (this.state.name === '') {
        alert('待辦事項名稱未輸入!')
    }
    else {
        //判斷id是否有值
        if (this.state.id === '') {
            this.props.addTodoList(this.state)
            alert('成功新增!')
        }
        else {
            //有的話就執行編輯
            this.props.editTodoList(this.state)
            alert('編輯成功!')
        }
        this.setState({ id: '', name: '', date: '', time: '', file: '', commit: ''
                        , important: '', complete: false })
        this.filebox.current.value = ''
        this.props.closeAdd()
    }
}

上方我是編輯後就將表單關掉,如果表單要停留在編輯畫面的話就把最後那三行放到alert('成功新增!')下面。

搞定後來看看結果吧!
修改前:
https://ithelp.ithome.com.tw/upload/images/20181029/20106935rxwSzp1Eq3.png

修改後:
https://ithelp.ithome.com.tw/upload/images/20181029/201069350gqqt0yabn.png

資料就變了,點開也是修改後的資料:
https://ithelp.ithome.com.tw/upload/images/20181029/20106935ZOJq9wWwat.png

有沒有覺得大功告成的感覺XD,其實因為是第一次實作組件開發,所以程式中應該還有不少地方可以做優化,如果各位大大有任何建議或是小弟我使用方法錯誤,還請留言告訴我,那以下附上今天的GitHub進度:
GitHub程式目錄連結
GitPage頁面連結


剩下的明天會把輸出的排序處理好,因為標記重要的在最上面,已完成的要在底部,最後一天的話就直接把另外兩個頁面處理完,一個輸出已完成、另一個輸出重要的事項,就剩最後兩步了!

那感謝各位大大的觀看,如果有任何問題會是需要改進的地方,還請大家留言告訴我,小弟會盡快修改或補充文章內容,謝謝大家/images/emoticon/emoticon41.gif


上一篇
[筆記][React]來做個作品吧!待辦事項「todolist」篇(7)-讓組件重用起來
下一篇
[筆記][React]來做個作品吧!待辦事項「todolist」篇(9)-把資料做排序吧
系列文
一步一腳印的React旅程30

尚未有邦友留言

立即登入留言