iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 13
5
Modern Web

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

[筆記][React]當React遇上Redux(1)-初次見面

Hello!大家好啊!就算標題讓人搞不清楚狀況,但是點進來的時候應該還沒忘記昨天的成就感吧XD,為了能夠在最後獲得更大的滿足,今天再來開始繼續學習吧!


介紹Redux

在介紹Redux之前我必須先說,他和React是個完完全全沒有任何關係的套件,也就是說就算沒有ReduxReact我們一樣也可以寫得很開心,但是相信我,如果有他在的話,開發React一定會更開心...其實我也不確定XD,但我們一起來見證吧!

Redux是給JavaScript管理一些我們已經知道的值,也就是說可以把它當成一個管理資料的容器,並可以讓分散在不同地方的各個組件自由取用,使用Redux好處和從父組件傳props到子組件的道理一樣,讓資料擁有一致的特性。

開始使用

好的,從這裡開始又是一段新的旅程,我們需要花幾跟篇福來說明,因為這有點複雜,所以我會盡量簡單並且由淺入深的解釋,請大家再跟著我一起認識Redux吧!

從起點開始

雖然時間很短,但還是請大家先忘記之前學的React,讓我們從一個新專案開始,不曉得各位還記不記得,首先從npm init開始,相信大家應該都很熟,所以接下來會快速帶過:

  1. 輸入npm init -y建立專案:
  2. 安裝webpack

    npm i webpack -dev
    npm i webpack-cli -dev
    npm i webpack-dev-server -dev

  3. 安裝babel,因為本篇不寫JSX所以先不下載@babel/preset-react

    npm i babel-loader -dev
    npm i @babel/core -dev
    npm i @babel/preset-env

  4. 建立webpack的設定檔「webpack.config.js」,並設定babel-loader
    const path = require('path');
         module.exports = {
         //這個webpack打包的對象
         entry: {
             index: ['./index.js']
         },
         output: {
             //這裡是打包後的檔案名稱
             filename: 'bundle.js',
             //打包後的路徑,這裡使用path模組的resolve()取得絕對位置,也就是目前專案的根目錄
             path: path.resolve('./'),
         },
         module:{
             rules:[
                 {test:/\.js$/,use:{loader:'babel-loader',options:{presets:['@babel/preset-env']}}}
             ]
         }
     };
    
  5. 然後是本篇的主角redux

    npm i redux
    https://ithelp.ithome.com.tw/upload/images/20181004/20106935ksdfODrXzw.png

  6. 最後是建立一個寫JavaScript的「index.js」和唯一的畫面「index.html」,index.html的內容為:
    <html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    </head>
    <body>
        <script src="bundle.js"></script>
    </body>
    </html>
    
  7. 完成後我們的目錄會長這樣:
    https://ithelp.ithome.com.tw/upload/images/20181004/20106935Gtxx5pLKDZ.png

前置作業

不是我想要湊字數,是最不能忘記的是記得要import下載的redux套件,這裡只需要從redux匯入createStore方法就好:

import { createStore } from "redux"

設定state物件

本文開頭有提到說Redux是用來管理JavaScript中的資料,所以想當然我們必須要先有個資料物件,就像我們昨天的訊息資料一樣,不過這裡先簡單存個num

import { createStore } from "redux"

//一開始拿到的資料
const data = {num : 1}

設定動作指令

這裡要設定的動作是異動資料的意思,像是更改資料內容、新增或刪除等等,但是看到這裡也許會覺得很奇怪,因為資料是從server端撈出來的,所以最源頭的資料應該是不能更改的!

沒錯!這個觀念是正確,不過我們稍後再說,這裡要設定的動作是一個建構器,他會產生一個物件,裡面存有兩個屬性,第一個type代表動作的指令,第二個payload則是執行的動作要傳入的資料,而這裡payload指定的值我們會藉由呼叫addNum的時候指定:

import { createStore } from "redux"

const data = {num : 1}

//動作指令設定
const addNum = article => ({ type: "addNum", payload: article })

託付資料,和執行動作

這邊就是精髓了,整個晚上幾乎都卡在這裡,所以我不會讓各位和我一樣的,一起搭上飛機吧!

有了資料和動作後,我們要試著把他們綁再一起,這時候會用一個匿名函式去處理它,在redux中稱它為Reducer,我們會在裡面描述每個動作對資料發生的事情,而因為要執行的動作通常不只一個,所以會用switch來判斷目前收到的指令(就是剛剛設定的type)為哪個動作:

import { createStore } from "redux"

const data = {num : 1}
const addNum = article => ({ type: "addNum", payload: article })

/*將一開始拿到的資料指定給第一個參數state,第二個參數action會在執行動作時傳入當初設定的指令物件,例如執行addNum時會將整個物件丟給action*/
const rootReducer = (state = data, action) => {
    //用switch.type來判斷指令為何
    switch (action.type) {
        case "addNum":
            /*如果是addNum的話就把初始數字加上payload的值,並回傳
            **注意這裡只做回傳,並不對原資料做異動*/
            return {num: state.num + action.payload }
        default:
            //沒有符合執行動作的條件就不做處理直接回傳
            return state
    }
}

Reducer放進store

這個store就是從一開始一直在說的,他會保管一個應用程式中所有的資料,所以我們把剛剛設定的ReducercreateStore交給他吧!

import { createStore } from "redux"

const addNum = article => ({ type: "addNum", payload: article })
const data = {num : 1}

const rootReducer = (state = data, action) => {
  switch (action.type) {
    case "addNum":
      return {num: state.num + action.payload }
    default:
      return state
  }
}
//託付剛剛的一番辛苦完成的Reducer
const store = createStore(rootReducer)

測試了!

在這裡因為我們只是單純用redux而已,所以也沒辦法用什麼組件去觸發剛剛做的store,不過別擔心,在這個階段就先把剛剛宣告的addNum動作和保管著資料的store加入全域中,讓我們可以直接在console中執行:

import { createStore } from "redux"

const addNum = article => ({ type: "addNum", payload: article })
const data = {num : 1}

const rootReducer = (state = data, action) => {
  switch (action.type) {
    case "addNum":
      return {num: state.num + action.payload }
    default:
      return state
  }
}
const store = createStore(rootReducer)

//這邊多加了一個data是想讓大家確認原本的資料是不會變的!
window.data = data;
window.store = store;
window.addNum = addNum;

在全域中設定完後就可以用webpack -p打包index.js檔了,打包完後不論是直接打開index.html或是用webpack-dev-server都可以,開啟後就可以進行測試。

.getState()

這個函式可以看到目前store()所保管的資料內容:
https://ithelp.ithome.com.tw/upload/images/20181005/20106935H9tBGBXeSR.png

.subscribe()

這個函式是store的監聽器,他會需要一個function當引數,用來在store保管的資料發生改變的時候執行,以下我們先設定監聽:
https://ithelp.ithome.com.tw/upload/images/20181005/20106935GWcFdUNzVj.png

.dispatch()

這裡我們就來傳入剛剛設定的方法吧!另外記得我們在設定addNum的時候為他留了一個參數嗎?現在也不要忘了指定他,不然payload就會是undefined了:
https://ithelp.ithome.com.tw/upload/images/20181005/20106935jJtBFB2SGa.png

登愣!看到這一刻真的會有感動的感覺,當我執行動作時他給了我兩個回應,第一個是我們剛剛用.subscribe()設定的監聽器,沒錯!資料被改變了!而第二個就是透過addNum建構出來的物件,這個不重要,重要的是我們再一次執行store.getState()來確認被保管的資料如何了:
https://ithelp.ithome.com.tw/upload/images/20181005/2010693589fjLVgGGL.png

果然華麗的加上2了!最後再來確認原本的資料是不是還是最初的樣子:
https://ithelp.ithome.com.tw/upload/images/20181005/20106935DdMOm2WO6P.png

這是本篇的GitHub程式碼:
GitHub連結

是不是覺得這一切有點棒!雖然目前做到的事情感覺不多,但至少我們已經瞭解了在redux中他是透過Reducer來設定事件的觸發、store來保管資料,且可以利用.getState()隨時確認資料內容、並在使用.dispatch()發生改變的時候透過.subscribe()監聽並執行指定的事件,當我們對redux有了初步的認識後,明天就能夠試著把他加入react中!請各位敬請期待!包括我也很期待XD


最後感謝各位大大的觀看,畢竟小弟也是第一次學習,所以不論文章中有任何錯誤或是解釋不清楚的地方,還麻煩各位大大留言告訴我,我會在盡快補充或修正文章內容的!謝謝大家/images/emoticon/emoticon41.gif

參考文章:

  1. https://chentsulin.github.io/redux/index.html
  2. https://www.valentinog.com/blog/react-redux-tutorial-beginners/#React_Redux_tutorial_refactoring_the_reducer

上一篇
[筆記][React]使用React開發的思考模式(3)-最後來個小範例吧!
下一篇
[筆記][React]當React遇上Redux(2)-資料的傳遞方式
系列文
一步一腳印的React旅程30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

1
SunAllen
iT邦研究生 1 級 ‧ 2018-10-19 10:10:58

大大,您的意思是React 和 Redux 的關係,對開發者來說,類似蛋和蔥花嗎XD

蔥花蛋比蛋好吃啊~

神Q超人 iT邦研究生 5 級 ‧ 2018-10-19 15:11:14 檢舉

沒錯!
如果網頁是薯條,React就像番茄醬、Redux就像糖包,雖然薯條加番茄醬就不錯了,但是再加糖包的隱藏吃法更讚XD

SunAllen iT邦研究生 1 級 ‧ 2018-10-19 15:14:43 檢舉

麥當勞/images/emoticon/emoticon02.gif
好餓

神Q超人 iT邦研究生 5 級 ‧ 2018-10-20 00:40:11 檢舉

噓!不能業配啦/images/emoticon/emoticon37.gif

我要留言

立即登入留言