@(Redux)[|用法|原始碼]
Redux 由Dan Abramov在2015年建立的科技術語。是受2014年Facebook的Flux架構以及函數語言程式設計語言Elm啟發。很快,Redux因其簡單易學體積小短時間內成為最熱門的前端架構。
@[三大原則]
- 單一資料來源 - 整個應用的
state
被儲存在一棵object tree
中,並且這個object tree
只存在於唯一一個store
中。所有資料會通過store.getState()
方法呼叫獲取. - State只讀 - 根據
State
只讀原則,資料變更會通過store,dispatch(action)
方法. - 使用純函式修改 -
Reducer
只是一些純函式,它接收先前的state
和action
,並返回新的state
.
[TOC]
準備階段
柯里化函式(curry)
//curry example
const A = (a) => {
return (b) => {
return a + b
}
}
複製程式碼
通俗的來講,可以用一句話概括柯里化函式:返回函式的函式. 優點: 避免了給一個函式傳入大量的引數,將引數的代入分離開,更有利於除錯。降低耦合度和程式碼冗餘,便於複用.
程式碼組合(compose)
舉個例子
let init = (...args) => args.reduce((ele1, ele2) => ele1 + ele2, 0)
let step2 = (val) => val + 2
let step3 = (val) => val + 3
let step4 = (val) => val + 4
let steps = [step4, step3, step2, init]
let composeFunc = compose(...steps)
console.log(composeFunc(1, 2, 3))
// 1+2+3+2+3+4 = 15
複製程式碼
接下來看下FP思想的compose的原始碼
const compose = function (...args) {
let length = args.length
let count = length - 1
let result
let this_ = this
// 遞迴
return function f1(...arg1) {
result = args[count].apply(this, arg1)
if (count <= 0) {
count = length - 1
return result
}
count--
return f1.call(null, result)
}
}
複製程式碼
通俗的講: 從右到左執行函式,最右函式以arguments為引數,其餘函式以上個函式結果為入引數執行。
優點: 通過這樣函式之間的組合,可以大大增加可讀性,效果遠大於巢狀一大堆的函式呼叫,並且我們可以隨意更改函式的呼叫順序
CombineReducers
作用
隨著整個專案越來越大,state
狀態樹也會越來越龐大,state的層級也會越來越深,由於redux
只維護唯一的state
,當某個action.type
所對應的需要修改state.a.b.c.d.e.f
時,我的函式寫起來就非常複雜,我必須在這個函式的頭部驗證state
物件有沒有那個屬性。這是讓開發者非常頭疼的一件事。於是有了CombineReducers
。我們除去原始碼校驗函式部分,從最終返回的大的Reducers
來看。
Note:
- FinalReducers : 通過
=== 'function'
校驗後的Reducers
.- FinalReducerKeys :
FinalReducers
的所有key
(與入參Object
的key
區別:過濾了value
不為function
的值)
原始碼
// 返回一個function。該方法接收state和action作為引數
return function combination(state = {}, action) {
var hasChanged = false
var nextState = {}
// 遍歷所有的key和reducer,分別將reducer對應的key所代表的state,代入到reducer中進行函式呼叫
for (var i = 0; i < finalReducerKeys.length; i++) {
var key = finalReducerKeys[i]
var reducer = finalReducers[key]
// CombineReducers入參Object中的Value為reducer function,從這可以看出reducer function的name就是返回給store中的state的key。
var previousStateForKey = state[key]
// debugger
var nextStateForKey = reducer(previousStateForKey, action)
// 如果reducer返回undefined則丟擲錯誤
if (typeof nextStateForKey === 'undefined') {
var errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
// 將reducer返回的值填入nextState
nextState[key] = nextStateForKey
// 如果任一state有更新則hasChanged為true
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
複製程式碼
小結
combineReducers
實現方法很簡單,它遍歷傳入的reducers
,返回一個新的reducer
.該函式根據State
的key
去執行相應的子Reducer
,並將返回結果合併成一個大的State
物件。
CreateStore
作用
createStore
主要用於Store
的生成,我們先整理看下createStore
具體做了哪些事兒。(這裡我們看簡化版程式碼)
原始碼(簡化版)
const createStore = (reducer, initialState) => {
// initialState一般設定為null,或者由服務端給預設值。
// internal variables
const store = {};
store.state = initialState;
store.listeners = [];
// api-subscribe
store.subscribe = (listener) => {
store.listeners.push(listener);
};
// api-dispatch
store.dispatch = (action) => {
store.state = reducer(store.state, action);
store.listeners.forEach(listener => listener());
};
// api-getState
store.getState = () => store.state;
return store;
}
複製程式碼
小結
原始碼角度,一大堆型別判斷先忽略,可以看到宣告瞭一系列函式,然後執行了dispatch
方法,最後暴露了dispatch
、subscribe
……幾個方法。這裡dispatch
了一個init Action
是為了生成初始的State
樹。
ThunkMiddleware
作用
首先,說ThunkMiddleware
之前,也許有人會問,到底middleware
有什麼用?
這就要從action
說起。在redux
裡,action
僅僅是攜帶了資料的普通js
物件。action creator
返回的值是這個action
型別的物件。然後通過store.dispatch()
進行分發……
action ---> dispatcher ---> reducers
同步的情況下一切都很完美……
如果遇到非同步情況,比如點選一個按鈕,希望1秒之後顯示。我們可能這麼寫:
function (dispatch) {
setTimeout(function () {
dispatch({
type: 'show'
})
}, 1000)
}
複製程式碼
這會報錯,返回的不是一個action
,而是一個function
。這個返回值無法被reducer
識別。
大家可能會想到,這時候需要在action
和reducer
之間架起一座橋樑……
當然這座橋樑就是middleware
。接下來我們先看看最簡單,最精髓的ThunkMiddleware
的原始碼
原始碼
const thunkMiddleware = ({ dispatch, getState }) => {
return next => action => {
typeof action === 'function' ?
action(dispatch, getState) :
next(action)
}
}
複製程式碼
非常之精髓。。。我們先記住上述程式碼,引出下面的ApplyMiddleware
ApplyMiddleware
作用
介紹applyMiddleware
之前我們先看下專案中store
的使用方法如下:
let step = [ReduxThunk, middleware, ReduxLogger]
let store = applyMiddleware(...step)(createStore)(reducer)
return store
複製程式碼
通過使用方法可以看到有3處柯里化函式的呼叫,applyMiddleware
函式Redux
最精髓的地方,成功的讓Redux
有了極大的可擴充空間,在action
傳遞的過程中帶來無數的“副作用”,雖然這往往也是麻煩所在。 這個middleware
的洋蔥模型思想是從koa
的中介軟體拿過來的,用圖來表示最直觀。
洋蔥模型
我們來看原始碼:原始碼
const applyMiddleware = (...middlewares) => {
return (createStore) => (reducer, initialState, enhancer) => {
var store = createStore(reducer, initialState, enhancer)
var dispatch
var chain = []
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
// 每個 middleware 都以 middlewareAPI 作為引數進行注入,返回一個新的鏈。
// 此時的返回值相當於呼叫 thunkMiddleware 返回的函式: (next) => (action) => {} ,接收一個next作為其引數
chain = middlewares.map(middleware => middleware(middlewareAPI))
// 並將鏈代入進 compose 組成一個函式的呼叫鏈
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
複製程式碼
applyMiddleware
函式第一次呼叫的時候,返回一個以createStore
為引數的匿名函式,這個函式返回另一個以reducer
,initialState
,enhancer
為引數的匿名函式.我們在使用方法中,分別可以看到傳入的值。
結合一個簡單的例項來理解中介軟體以及洋蔥模型
// 傳入middlewareA
const middlewareA = ({ dispatch, getState }) => {
return next => action => {
console.warn('A middleware start')
next(action)
console.warn('A middleware end')
}
}
// 傳入多個middlewareB
const middlewareB = ({ dispatch, getState }) => {
return next => action => {
console.warn('B middleware start')
next(action)
console.warn('B middleware end')
}
}
// 傳入多個middlewareC
const middlewareC = ({ dispatch, getState }) => {
return next => action => {
console.warn('C middleware start')
next(action)
console.warn('C middleware end')
}
}
複製程式碼
當我們傳入多個類似A,B,C的middleware
到applyMiddleware
後,呼叫
dispatch = compose(...chain)(store.dispatch)
複製程式碼
結合場景並且執行compose
結果為:
dispatch = middlewareA(middlewareB(middlewareC(store.dispatch)))
複製程式碼
從中我們可以清晰的看到middleware
函式中的next
函式相互連線,這裡體現了compose
FP程式設計思想中程式碼組合的強大作用。再結合洋蔥模型的圖片,不難理解是怎麼樣的一個工作流程。
最後我們看結果,當我們觸發一個store.dispath
的時候進行分發。則會先進入middlewareA
並且列印A start
然後進入next
函式,也就是middlewareB
同時列印B start
,然後觸發next
函式,這裡的next
函式就是middlewareC
,然後列印C start
,之後才處理dispath
,處理完成後先列印C end
,然後B end
,最後A end
。完成整體流程。
小結
Redux applyMiddleware
機制的核心在於,函數語言程式設計(FP)
的compose
組合函式,需將所有的中介軟體串聯起來。- 為了配合
compose
對單參函式的使用,對每個中介軟體採用currying
的設計。同時,利用閉包原理做到每個中介軟體共享Store
。(middlewareAPI
的注入)
Feedback & Bug Report
- github: @同性交友網站
Thank you for reading this record.