我以爲redux對於初學者並非很友好,不少概念都不太好理解,會使用redux以後有必要看源碼javascript
redux的store存儲了應用的狀態樹,要改變state
只能經過dispatch()
方法。html
雖然文檔中明確指出store只能有一個,可是我在工做的項目中store有可能不止一個,好比多頁面應用。java
createStore
有3個參數,分別是reducer
、preloadedState
、enhancer
。git
reducer
。這裏傳入的reducer
一般是經過combineReducers
集成的reducerpreloadedState
。初始化store時候頗有必要傳入一個初始化的state,一來能夠給應用頁面一個初始值,二來可讓本身或者別人瞭解整個app的state結構。enhancer
。一般就是一些redux的中間件,中間件的概念有點繞。createStore
作了參數校驗和類型檢測,除了reducer是必須傳入以外,其他的兩個參數都不是必須的。 傳入的形式包含如下幾種。github
createStore(reducer)
createStore(reducer, enhancer)
createStore(reducer, preloadedState)
createStore(reducer, preloadedState, enhaner)
createStore的內部包含了3個重要的變量和4個供外部調用的方法。redux
currentState
。存儲了整個store管理的狀態樹。currentListeners
。redux實際上是觀察者模式的一個實踐。咱們能夠把currentListeners看作state變化後待執行的函數列表。isDispatching
。是否正在進行dispatch
。nextListners
。當進行subscribe
操做時候,先把新的listener函數push到nextListners
數組中,做爲一個最新listener數組快照。currentState
內部變量直接返回。function getState() {
return currentState;
}
複製代碼
nextListners
。每次進行dispatch
方法時候,nextListenrs
替換currentListeners
,傳入的listener會被執行。一般來講咱們會把UI的渲染,做爲listener傳入到subscribe中,每當state變化,redux會通知UI進行render。 listener不會看到每個全部state的變化,由於state可能會在dispatch中變化屢次後,listener才被執行。 subscribe
方法返回一個函數閉包,做爲取消訂閱。function subscribe() {
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
複製代碼
plain object
action(普通的JavaScript對象),若是要傳遞一個thunk,須要使用中間件,redux-thunk。function dispatch(action) {
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
// 把nextListeners替換currentListeners
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
複製代碼
把多個reducer合到一個函數中。combineReducers會調用每個reducer,並把每個reducer返回的state合併到state樹中。數組
類型爲一個object。每一個鍵對應的必須爲一個reducer
函數。bash
const params = {
key1: reducerfunc1,
key2: reducerfunc2,
};
複製代碼
假設reducerfunc1和reducerfunc2返回的state形式分別爲閉包
const state1 = {
v1: '',
v2: 0,
};
const state2 = [];
複製代碼
那麼咱們的state樹就是這樣形式,經過getState
獲取到的state對象以下。app
const state = {
key1: {
v1: '',
v2: 0,
},
key2: [],
}
複製代碼
閉包以前的代碼都是reducer檢測
代碼很好理解。
export default function combineReducers(reducers) {
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]
// reducer調用前的state
const previousStateForKey = state[key]
// reducer調用後的state
const nextStateForKey = reducer(previousStateForKey, action)
// 更新state樹快照
nextState[key] = nextStateForKey
// state是否有變更的flag。若reducer執行致使state變更,返回一個全新的state對象,因此能夠直接比較對象來判斷是否有改變。
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
}
複製代碼
中間件,我以爲是整個redux中比較有意思,而且稍微有點難理解的部分。
能夠在官網查看中間件的文檔。 能夠把中間件理解爲在dispatch
方法的先後作一些操做。也能夠類比爲java的切面。
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = () => {
throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
複製代碼
其實這裏能夠理解爲俄羅斯套娃,原始的store.dispatch就是套娃的最裏面那個,全部的中間件按照數組的順序,一個把一個套住。而這個套娃的過程由compose完成。
要理解這部分,咱們把中間件redux-thunk的源碼也拿過來看看。 redux-thunk讓咱們的action爲函數。注意!! 本來的action只能是一個plain object。 如下是redux-thunk中間件的寫法,必須傳入store,dispatch,action後纔會執行真正的函數實體。 這裏用的是柯里化
,只有參數夠了,纔會去執行函數體。
// 稍微改動過的redux-thunk
const thunk = ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
};
export default thunk;
複製代碼
我這裏一開始看不懂,爲何一開始dispatch賦值爲一個空函數? 其實compose完成以後會返回一個新的dispatch,這個dispatch會替換掉那個空函數
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
複製代碼
最後返回store變量。
return {
...store,
dispatch
}
複製代碼