通俗易懂的Redux瞭解下

小美娜娜發表於2018-09-13

Redux讓我腦仁疼,感覺搞不定他。因為Redux對我而言太抽象了,所以我用通俗易懂地方法去思考Redux,感覺能夠理解Redux一些了。

本文要點:

  • action
    • 配置行為
    • store.dispatch(action)
  • reducer
    • 純函式
    • 返回新的state
    • createStore(reducer)
  • store
    • getStore
    • dispatch
    • subscribe
  • combineReducers

本文程式碼倉庫,在此資料夾之中。

講道理我要用import和export的寫法來學習Redux,但是node有些es6還是不支援的,為了簡單演示,我就用requre和module.exports來演示。(不然我還得講解下babel轉換es6的知識點)

在開始之前,我們是不是應該安裝一個redux,然後才能開始愉快地玩耍。

npm install --save redux
複製程式碼

action 和 reducer 一對好基友

用Redux建立全域性應用資料管理store之前,我們需要建立兩個型別的JS檔案,一個是action一個是reducer,這兩個型別的功能我個人認為很容易搞混。不知道哪個是幹什麼,經常忘記用法。那我只能通過他們的具體含義來判斷他們的用法,而不是死記硬背(哎,年紀大了,記性不好,只能通過理解來記住了。)

一個只知道安排任務的action

首先我們先看action,相信這個單詞大家都認識。導演拍戲的時候,都喜歡說action!,這個既有行動的意思,也有開始的意思,在Redux中就是開始行動的意思。也就是說這個action是主動,而非被動的。導演說開始,你就要開始,而且還要按照劇本進行,一切都被安排地明明白白。所以,Redux的action,就是一個指示,告訴store,我要進行這個東西,你給安排下。有時候,我們需要附帶點內容到store,才能進行下一步操作,這個時候就需要action攜帶規範的引數。

所以我們怎麼寫action呢?包含兩點:

  • 註冊行為
  • 註冊行為所帶的引數

註冊行為,安排行為,標準每個行為的指令

const todo={
  TOGGLE_TODO:'TOGGLE_TODO',
  GET_TODOS:'GET_TODOS'
}
複製程式碼

配置行為所帶的引數,標準化引數,該帶上的都帶上,以免被退回。其中type是行為的型別,必須有,就像有些office要帶門卡一樣,忘了就再見!要被保安請出去的。後方的item就是自定義的,看看你的操作需要哪些引數,需要就帶上,不需要帶上的就不要帶上了,哪樣就很累贅了。

const todo={
  TOGGLE_TODO:'TOGGLE_TODO',
  GET_TODOS:'GET_TODOS',
  toggleTodo:function ({ items,id }) {
    return {
      type: todo.TOGGLE_TODO,
      items:items
      id:id
    }
  },
  getTodos: function getTodos({items}){
    return {
      type:todo.GET_TODOS,
      items:items
    }
  }
}
module.exports=todo
複製程式碼

action只是告訴你發生了什麼,給你配齊了引數,但是不執行,不對傳入的物件(state)進行改變。*

但是既然action僅僅是配置而已,那麼怎麼操作呢,之後怎麼進行呢?Redux才不會這麼好心幫我們把後續操作給搞定了,這個時候我們就要去研究Reducer了,一個我不知道如何翻譯的函式。

純純的reducer

現在我們來看最無法理解的reducer,為什麼他要叫reducer,有什麼具體含義嗎?還是隨便起的名字,總有一個原因吧,就和爸媽給我們取名字一樣總是寄予一些美好的期盼。

reducer的含義有很多,很多領域都用這個單詞,但是在Redux中,這應該是函式式語言(functional languages)中的一個概念。在wiki中,通常稱為Fold,作為一個結構轉變(structural transformations)的一個方式。

其他語言Reducer實現方式我們不管,我們只看JS,在JS中出現的array.reduce(func,initval),相信大家應該很熟悉,也是一個剛上手就想哭的一個方法。陣列中的reduce有什麼特點呢(不會用reduce的小夥伴,點選這裡):

  • 給出了一個初始initval
  • 有一個轉換的func方法,這個方法必須是純函式,既不改變傳入物件的屬性
  • 最終返回一個新的物件/值,而array保持不變。

從而可以推論出Redux中的reducer的特點:

  • 需要一個初始值,在redux中就是初始的state
  • 有一個轉換的方法,這個方法必須是純函式
  • 最終返回一個新的物件,也就是新的state,而傳入的state保持不變。

大家可能對純函式(pure funciton)有些模糊,函式還能pure??以為是純牛奶呢??這裡的pure的意思就是他是乾乾淨淨的,毫無雜質。也就是一下兩點:

  • 不改變傳入的state的屬性,即不影響函式外的引數
  • 無副作用(side effect),比如觸發了某個事件,或者捕獲了什麼值。也就是與外界絕緣,我就守好我的二畝三分地就好了。

大家想深入瞭解的話,雙手奉上wiki連結

那麼我們該怎麼寫呢?

大家複習下上面的action,reducer可是action配套的好兄弟,一個在前,一個在後並肩作戰。

reducer並不是主動執行,而是被動執行的,來了一個action,然後reducer處理下。

簡而言之:reducer就是響應來自action的召喚,然後返回一個新的物件(state)。

首先先匯入來自action的行為,這行為是被註冊的,是可以執行的。

const {TOGGLE_TODO,GET_TODOS}=require('../actions/Todo')
複製程式碼

reducer的一個定義就是一定有個初始值,這裡設定一個也就是以防萬一。

const initialState={}
複製程式碼

重點來了,reducer開始過濾註冊的行為們,然後每次都是一個個配對,遇上配對的,就執行對應的程式碼,然後返回一個新的state,如果輪了一邊發現這是未註冊的行為,為了防止這個行為回去不好交代,就把原始的state給他帶回去交差。

module.exports=function(state = initialState, action) {
    switch(action.type){
        case GET_TODOS:
            return {todos:[...action.items]}
        case TOGGLE_TODO://deleted=true
            return {
                todos:action.items.map((i)=>{
                    if(i.id===action.id){
                        return {
                            ...i,
                            status:i.status===0?1:0
                        }
                    }else{
                        return {
                            ...i
                        } 
                    }
                })
            }
        default:
            return state;
    }
}
複製程式碼

reducer根據action的指令,進行一系列操作,但是不改變原有的傳入物件(state),而是返回一個新的物件(state)。也就是Reducer,進行純計算,沒有非同步,沒有汙染,出來的值沒有意外,就像1+1一定等於2一樣,然後返回一個嶄新嶄新的物件(state)!

拜見老大store

雖action和reducer是一對基,配合默契,但是他們都是兩個獨立的模組,怎麼醬他們連結起來,還需要一個橋樑。這個就是store,redux中真正的老大,大家都要通過store來執行下一步的人任務,以及要聽從store派發下來的任務。

我們先配置一個老大吧,todoDemoList是假資料,大家懂的測試用。老大隻需要reducers這群小弟來建幫派createStore。action再幫派建立之初並無用處。

const {createStore}= require('../../node_modules/redux')
const todo = require('./reducers/Todo')
const {toggleTodo,getTodos} = require('./actions/Todo')

let todoDemoList=(new Array(20)).join("|").split("|").map((v,i)=>{
    return {id:i,content:"demo"+(i+1),status:i%4===0?1:0}
})

const store=createStore(todo)
複製程式碼

我們的store究有何種能立呢?

  • 掌控整個應用的資料state,老大當然是掌控全域性的啦,沒有什麼能瞞住他

  • 如果想要訪問應用資料state,那麼就要給store提申請,這個申請就是getState()

console.log(store.getState())//列印出來是一個空的{},當然啦!我們都沒有給老大進貢,拿來的資料
複製程式碼
  • 想要修改資料,那更要給老大提申請了,那就是dispatch(你的申請action)。action終於出場啦!
store.dispatch(getTodos({items:todoDemoList}))//給老大進貢
console.log(store.getState().todos[1])//有資料了
store.dispatch(toggleTodo({items:todoDemoList,id:1}))//修改某一個小羅羅
console.log(store.getState().todos[1])//修改成功
複製程式碼
  • 不要以為你修改的時候可以為所欲為,老大看著呢。老大通過subscribe(監視器listener)看著你呢,小樣給我注意點。不過此刻要注意,我們要把監聽器放在所有的dispatch執行之前,不然就捕捉不到dispatch了。要在一切還沒發生之前,就準備好,不然就要給小樣們鑽空子了。就像盜竊一樣,小偷已經偷完跑路了,你再裝監控,也逮不住這個小偷啊。
store.subscribe(()=>{
    console.log(store.getState())//監控這手下的一舉一動
})
複製程式碼
  • 老大也不是不近人情之輩,如果說你的表現夠好,還是會撤銷監視的。這個撤銷的方法就是store.subscribe()的返回值,這個返回值是一個方法,就像這樣store.subscribe()()
let unsubscribe=store.subscribe(()=>{
    console.log(store.getState())//監控這手下的一舉一動
})
store.dispatch(toggleTodo({items:todoDemoList,id:1}))//老大在watching,小心翼翼。
unsubscribe()//放了你們,出去浪吧
store.dispatch(toggleTodo({items:todoDemoList,id:1}))//居然真的沒有再繼續監視我了,放飛自我。
複製程式碼

老大怎麼可能只有一個小弟reducer呢?當然有很多啦!combineReducers(小弟們)

一個幫派只能有一個老大!但是可以有很有小弟,每個小弟各司其職。但是要將這些小弟統一管理起來,這個時候就要用combineReducers了。

用相同的方法建立一個filterAction,然後用combineReducers一下,接下來就可以愉快地合作了。

...
const filterAction = require('./reducers/filterAction')
const {showCompleted} = require('./actions/filterAction')
...
const rootReducer = combineReducers({todo: todo, filterAction: filterAction})
....
store.dispatch(showCompleted({items:todoDemoList}))//success執行啦!
複製程式碼

相關文章