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執行啦!
複製程式碼