iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 15
4
Modern Web

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

[筆記][React]當React遇上Redux(3)-從React組件操作Reducer

Hihi!大家好!這樣子的開頭還要在鐵人賽結束前繼續用下去,請大家多多包涵XD,昨天我們已經能夠從Redux中取到資料了,今天要來說說該怎麼把透過React來觸發寫在Reducer中的事件吧!


開始之前要先前情提要一下,這個目錄是昨天將留言資料給列出來的最後結果,今天要從這個進度開始,把留言功能給補上去!

store事件處理

既然要對store中的資料做處理,那在兩天前的文章中也有提到說,必須先將寫好要觸發事件的指令物件,再透過Reducer描述哪些指令分別要處理什麼事情,所以我們先來到index.js中把事件寫好!

昨天的程式碼中,我們有記錄著一個addMessage的指令:

//設定動作
const addMessage = article => ({type:'addMessage',payload:article})

不過這個指令還沒有在Reducer中描述該做哪些事情,今天就來把它寫好吧!

//Reducer
const rootReducer = (state = data, action) => {
    //由action傳入的事件判斷指令為何
    switch (action.type) {
        //如果接收到addMessage的話
        case "addMessage":
            /*指定key值為現有長度+1,如果有刪除功能就不能這麼取了,
            但現在沒有,所以就簡單做*/
            action.payload.key = String(state.message.length+1)
            //這裡把接收到的資料payload增加到message的陣列中,並回傳整個state的內容
            return { ...state, message: [...state.message, action.payload] }
        default:
            return state
    }
}

就這樣子,簡單地寫完addMessage的事件之後回傳一個被改變後的新state

才怪!

至少我是這麼覺得啦XD,如果對ES6的語法不太熟的大大,應該和我一樣會對下面這一行覺得困惑:

{ ...state, message: [...state.message, action.payload] }

到底什麼是...?他是ES6新增的語法,叫做「解構賦值(Destructuring assignment)」,他能做到將兩個陣列或是兩個物件做合併,並回傳一個新的陣列或物件。

例如上方那一行可以分成兩部分來看,第一個是合併陣列:

[...state.message, action.payload]

state.message這是原有陣列資料,後面的action.payload是我們要加入陣列的資料,透過...語法就能夠將action.payload放進state.message中,並回傳一個新陣列。

第二個部分就是物件

{ ...state, message: [...state.message, action.payload] }

他的原理也是一樣,增加一個message屬性到原本的state中,並回傳一個新的物件。

這時候大家可能會有一個疑問,為什麼要這麼麻煩?不能直接用.pushaction.payload加到陣列中就好嗎?

當然可以,但是這樣子就會改變從後端取來的正確資料了!從server傳過來的資料應該讓他保持原貌會比較好!

寫好後,記得之前讓Reactmessage.jsx檔案接收到store的資料時做了什麼事情嗎?就是export default!所以現在message.jsx也要能夠接收到我們設定的動作,就必須也把上方設定的指令addMessage給匯出!

但是上一篇我們已經將index.js的匯出物件設定defaultstore了,現在要匯出一個以上的物件,我們得把default給拿掉,所以在index.js的最後一行把export default store給改成:

export {store,addMessage}

Redux這邊的處理就先告一段落了!接下來繼續修改message.jsx

使用事件

再進行處理前記得要先將剛剛從idnex.js匯出的addMessageimport進來:

import {addMessage} from "./index.js"

另外這邊要注意,因為store不再是index.js檔案default匯出的物件了,所以import store的這一行要改寫成:

import {store} from "./index.js"

最後整理一下因為這兩行都是從.index.js中匯入的,所以可以寫成這樣比較好看:

import {store,addMessage} from "./index.js"

//上一回同時從react-redux匯進來的Provider和connect也可以順便整理
import {Provider,connect} from "react-redux"

因為我們要寫的是留言的功能,所以簡單用React寫一個組件來讓使用者輸入:

class InputMessage extends React.Component {
    constructor(props){
        super(props)
        this.state = ({name:'',message:''})
        this.clearMessage = this.clearMessage.bind(this)
        this.changeState = this.changeState.bind(this)
        this.submitMessage = this.submitMessage.bind(this)
    }

    changeState(event){
        this.setState({[event.target.name]:event.target.value})
    }

    clearMessage(){
        this.setState({name:'',message:''})
    }

    submitMessage(){
        /*key值在這邊先給空的,新值會由reducer中處理給他*/
        let messageData = {
            key:'',
            name:this.state.name,
            message:this.state.message,
        }
        this.props.addMessage(messageData)
        this.clearMessage()
    }

    render(){
        return(
            <div>
                暱稱:<input type="text" name="name" 
                            value={this.state.name}
                            onChange={this.changeState} />
                <br/>
                訊息:
                <br/><textarea name="message" 
                                value={this.state.message}
                                onChange={this.changeState}></textarea>
                <input type="button" value="送出留言"
                        onClick={this.submitMessage} />
            </div>
        )
    }
}

上面都是之前學過的東西,應該沒什麼大問題,接下來就要實作該如何將事件this.props.addMessage(messageData)傳入這個InputMessage組件中:

先用mapDispatchToProps函式整理組件中需要的方法,這裡我們需要的是剛剛匯入的addMessage,所以建立他:

const mapDispatchToProps = dispatch => {
    return {
        addMessage: article => dispatch(addMessage(article))
      }    
}

mapDispatchToProps函式可以透過connect的第二個參數傳遞,而因為該組件並不需要取得任何資料,所以在沒有第一個參數mapStateToProps的情況下直接傳入null

const Input = connect(null,mapDispatchToProps)(InputMessage)

但是因為昨天已經有用connect處理過MessageList的部分:

const List = connect(mapStateToProps)(MessageList)

所以既然這兩個組件到最後都會放到MessageForm中,那為何不直接先放進去後再做一次connect呢?

沒錯!我們可以把程式反過來,昨天是先把MessageListconnectList後再放進MessageForm中,但如果MessageForm中的組件一多,用在connect的程式碼就會越來越多,看了也不舒服,所以不如先將所有需要connect的組件都先放到MessageForm中,最後在一次將MessageFormconnect處理就好!

首先要將昨天的MessageForm改寫,我順便在名稱前方加上Connect,表示是需要用來經過connect處理的,處理完後產生的組件再直接命名成MessageForm,這樣比較好辨識,不然命名完一個組件connect後又要再取一個名字實在有點麻煩XD:

class ConnectMessageForm extends React.Component {
    render(){
        return(
            <div>
                {/*把兩個組件放進來,一個需要資料一個需要事件
                這裡用props來傳,因為ConnectMessageForm等等會被connect
                資料也是傳到他的props中*/}
                <InputMessage addMessage={this.props.addMessage} />
                <MessageList data={this.props.data} />
            </div>
        )
    }
}

組件寫好就把他connect吧!在這時候把MessageList需要的資料和InputMessage需要的事件一併傳進去處理!

//connect第一個參數是資料,第二個是事件之後把結果放到MessageForm中
const MessageForm = connect(mapStateToProps,mapDispatchToProps)(ConnectMessageForm)

最後的最後,把MessageFormReactDOM.render()輸出到畫面上吧!貼心小提醒,記得要把MessageForm組件放在Provider中哦!

ReactDOM.render(<Provider store={store}>
                    <MessageForm />
                </Provider>,
                document.getElementById('root'))

大功告成啦!結果如下:
https://ithelp.ithome.com.tw/upload/images/20181007/20106935t6yMNTznWN.png

一個留言版就這麼誕生了!雖然還沒有寫CSS看起來有點兩光XD,被榨乾的現在我突然有點詞窮,哈哈哈哈,總之還是附上GitHub的連結:
GitHub程式目錄連結
GitPage連結

今天講的有點雜,不只多提到了mapDispatchToProps傳遞事件、用了一些ES6的語法、還改寫了昨天的程式,所以如果有任何問題都可以留言告訴我!


最後感謝各位大大的觀看,如果文章中有任何錯誤,或解釋不清楚的地方還麻煩留言告訴我,小弟我會盡快修正或補充文章內容的!謝謝大家/images/emoticon/emoticon41.gif

參考資料:

  1. https://www.valentinog.com/blog/react-redux-tutorial-beginners/#React_Redux_tutorial_refactoring_the_reducer
  2. https://redux.js.org/basics/usagewithreact

上一篇
[筆記][React]當React遇上Redux(2)-資料的傳遞方式
下一篇
[筆記][React]React的目錄結構篇
系列文
一步一腳印的React旅程30

尚未有邦友留言

立即登入留言