前言
redux是個狀態(tài)管理工具蝴簇,提供簡化的api做到行為的可預測性杯活,由于react在組件之間通信繁瑣,狀態(tài)管理需要狀態(tài)提升造成代碼冗長等局限熬词,redux常與react結合使用旁钧,但是redux作為獨立的框架吸重,其實是可以和其他的庫配合使用的,為了不被react混淆了redux的使用方法歪今,我們這里結合jq來理解redux的使用
redux基本概念
redux有三個基本原則
- 單一數據源:一個應用state只存在唯一一個對象樹當中(state狀態(tài)與試圖層級對應形成樹裝結構晤锹,所以稱為對象樹),由唯一的store統(tǒng)一管理
- state是只讀的:redux規(guī)定不能直接修改state,修改state需要觸發(fā)action,action* 是一個包含type字段的普通函數彤委,用來描述發(fā)生的事情
- 使用純函數修改state:通過action描述怎樣修改state,需要手動創(chuàng)建函數reducer(一個函數的返回結果只依賴于它的參數,并且在執(zhí)行過程里面沒有副作用或衡,我們就把這個函數叫做純函數)
const state ={
visibilityFilter: 'SHOW_ALL',
todos: [
{
text: 'Consider using Redux',
completed: true,
},
{
text: 'Keep all state in a single tree',
completed: false
}
]
}
我們可以根據之前定義的action更新state焦影,創(chuàng)建redux
const state ={
visibilityFilter: 'SHOW_ALL',
todos: [
{
text: 'Consider using Redux',
completed: true,
},
{
text: 'Keep all state in a single tree',
completed: false
}
]
}
function reducer(state=state,action){
switch(action.type){
case 'ADD_TODO':
return Object.assign({},state,{
todos:[
...state.todos,
{text:action.text,completed:false}
]
});
case 'SET_VISIBILITY':
return Object.assign({},state,{
visibilityFilter:action.filter
});
default:
return state
}
}
可以看到上面的兩個更新是可以獨立拆分出來的,分成兩個小的reducer封断,最后合成出一個完整的state
function reducer(state=state,action){
return {
todos:todos(state.todos,action),
visibilityFilter:visibilityFilter(state.visibilityFilter,action)
}
}
function todos(state=[],action) {
switch(action.type){
case 'ADD_TODO':
return [
...state,
{
text:action.text,
completed:false
}
]
default:
return state
}
}
function visibilityFilter(state='SHOW_ALL',action){
switch(action.type){
case 'SET_VISIBILITY_FILTER':
return action.filter
default:
return state
}
}
redux本身提供了一個方法combineReducers(reducers)斯辰,輔助我們根據給出的key值讓拆分出來的函數管理對應的state,返回一個完整的reducer坡疼,合并后的 reducer 可以調用各個子 reducer彬呻,并把它們返回的結果合并成一個 state 對象。
const reducer = combineReducers({
todos,
visibilityFilter
})
下面是combineReducers部分源碼
return function combination(state = {}, action) {
let hasChanged = false
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key]
const nextStateForKey = reducer(previousStateForKey, action)
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
通過上面的源碼可以看到柄瑰,在調用combineReducers方法后返回一個函數combination闸氮,這個combination就作為接下來要說的createStore的第一個必填參數傳入,生成store
createStore
cosnt store = createStore(reducer);
createStore接收三個參數教沾,reducers蒲跨、initState、enhancer(中間件)
createStore最重要的是在內部構造了四個方法授翻,分別是:
dispatch: 接收action或悲,更新狀態(tài)數。
getState: 得到當前最新state樹堪唐。
replaceReducer: 替換reducers巡语,初始化state樹。
subscribe: 用于觀察者模式淮菠,設置監(jiān)聽男公,并返回退出監(jiān)聽方法,在調用dispatch時兜材,運行所有被監(jiān)聽對象理澎。
createStore接受三個參數,第一參數reducers是必填曙寡,第二個參數initState用來初始化state糠爬,第三個參數用來合并中間件,這里我們分析一下store的四個方法和中間件举庶,initSate初始化狀態(tài)和服務端渲染沒有做過多研究执隧,之后補充。
createStore創(chuàng)建一個單一的store,用來管理唯一state,state是一個對象,store管理這個對象镀琉,通過store.getState返回最新的state峦嗤。
function getState() {
return currentState;
}
redux規(guī)定不能直接修改應用的狀態(tài) state,想要修改state只能通過dispatch方法,dispatch接受一個叫action的對象屋摔,里面必須包含一個 type 字段來聲明你到底想干什么烁设。redux相當于生成了一個共享,共享的狀態(tài)如果可以被任意修改的話钓试,那么程序的行為將非常不可預料装黑,所以我們提高了修改數據的門檻:你必須通過 dispatch 執(zhí)行某些允許的修改操作,而且必須大張旗鼓的在 action 里面聲明弓熏。
/**
*通過部分源碼可以看到dispatch接受action,然后調用reducers函數放回新的state更新當前的state
*至于怎么寫這個reducers有很多方式恋谭,不做本文重點,不做介紹挽鞠,可以看官方文檔疚颊,至于用哪種方式,怎么用就看個人喜好信认,用與不用它就在那里不悲不喜
*/
const dispatch = (action)=>{
currentState = reducers(currentState,action);
}
現在可以獲取到state并修改它材义,那我們想每次更新狀態(tài)的時候做一些統(tǒng)一的操作,比如打印出state看到變化狮杨,我們需要調用store.subscribe
nextListeners.push(listener);
return function unsubscribe() {
var index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
};
通過部分源碼我們可以看到母截,subscribe其實就是接收一個函數,然后把它存在一個數組橄教,每次dispatch調用時遍歷一遍這個數組
//dispatch部分源碼
for (var i = 0; i < listeners.length; i++) {
listeners[i]();
}
還有一個replaceReducer方法清寇,他接受一個新的reducers替換舊的reducers,然后初始化一個新的state
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer // 就是這么耿直簡單粗暴护蝶!
dispatch({ type: ActionTypes.INIT }) // 觸發(fā)生成新的 state 樹
}
dispatch與middleware
store里的dispatch函數只能接收一個action然后傳給reducer更新state然后重新渲染华烟,想要擴展一些操作就需要中間件來加強dispatch的功能,例如向服務器請求后進行后再進行更新state的操作持灰,可自己定義一個中間件來進行想要的強化盔夜,輸出不夠補輸出,防御不夠補護甲堤魁。
applyMiddleware(...middleware)
applyMiddleware方法接收多個中間件方法喂链,將多個中間方法合成一個新的dispatch,然后執(zhí)行新的dispatch時根據傳入順序逐個執(zhí)行
var store = createStore(reducer, preloadedState, enhancer)
var dispatch = store.dispatch // 指向原 dispatch
var chain = [] // 存儲中間件的數組
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
chain = middlewares.map(middleware => middleware(middlewareAPI)) //生成一個中間的數組
dispatch = compose(...chain)(store.dispatch)
return {
...store, // store 的 API 中保留 getState / subsribe / replaceReducer
dispatch // 新 dispatch 覆蓋原 dispatch妥泉,往后調用 dispatch 就會觸發(fā) chain 內的中間件鏈式串聯執(zhí)行
}
在上面代碼中最主要的就是compose函數椭微,它將個所有的中間件合并然后生成新的增強后dispatch,就像dispatch加了一層又一層的buff,為什么是說它是一層一層的是因為compose將所有的中間件組合生成新的dispatch就是洋蔥一樣將dispatch一層層包裹傳遞
function compose(...funcs) {
return arg => funcs.reduceRight((composed, f) => f(composed), arg);
}
我們調用compose函數將chain里的函數從右向左依次執(zhí)行,傳入的arg就是store.dispatch,假設我們添加了三個中間件最后生成的dispatch
dispatch = f1(f2(f3(store.dispatch))))
就像洋蔥一樣將dispatch一層層包裹,首先執(zhí)行f3然后將f3中間件的返回值是一個函數作為參數傳給傳給f2境氢,f2執(zhí)行后又包裹了一層f2的返回值锐借,然后傳給f1,最后f1將自己的返回值與之前兩個中間件的返回值組一起生成新的dispatch函數
我們用一個簡單的logger中間件理解一下這個過程
const logger = (store)=>(next)=>(action)=>{
console.log(store.getState());
next(action);
console.log(store.getState());
}
之前在applyMiddleware有這樣一行代碼
chain = middlewares.map(middleware => middleware(middlewareAPI))
它生出一個中間件的數組集合的同事其實也執(zhí)行了每個中間件,所以中間函數到下一層返回了一個函數
(next)=>(action)=>{
console.log(store.getState());
next(action);
console.log(store.getState());
}
而到了compose合并的時候返回的的新函數就是
(action)=>{
console.log(store.getState());
next(action);
console.log(store.getState());
}
理解這個過程很重要踏堡,這里執(zhí)行dispatch沒有直接調用store里的dispatch而是傳進去唱矛,是因為如果是多個中間件調用的話缺前,這里的next可能是上一層的(action)=>{}函數不是store.dispatch
我們在加一個logNext的函數將next打印出來看一下效果
const logNext = (store)=>(next)=>(action)=>{
console.log(next.toString());
next(action);
}
我們可以看到此時的next
function (n){console.log(e.getState()),t(n),console.log(e.getState())}
它的運行順序
--------------------------------------
| middleware1 |
| ---------------------------- |
| | middleware2 | |
| | ------------------- | |
| | | middleware3 | | |
| | | | | |
next next next ——————————— | | |
dispatch —————————————> | reducer | | | |
nextState <————————————— | | | | |
| | | ——————————— | | |
| | | | | |
| | ------------------- | |
| ---------------------------- |
--------------------------------------
在生成新的dispatch是層層包裹锅尘。層層進入监氢,層層冒出,就像是剝洋蔥
總結
本文介紹了redux的一些機制藤违,它可以幫助我們管理狀態(tài)忙菠,并且通過準守它的規(guī)定,可以讓狀態(tài)的修改可預料纺弊,并且實現了自動渲染,redux是優(yōu)秀的函數式編程應用骡男,體現了函數式編程的靈活性淆游,可以根據自己的需求自定義擴展,來管理使用隔盛。