做爲React全家桶的一份子,Redux爲react提供了嚴謹周密的狀態管理。但Redux自己是有點難度的,雖然學習了React也有一段時間了,自我感受算是入了門,也知道redux的大概流程。但其背後諸如creatstore,applymiddleware等API背後到底發生了什麼事情,我其實仍是不怎麼了解的,所以最近花了幾天時間閱讀了Redux的源碼,寫下文章紀錄一下本身看源碼的一些理解。(redux4.0版本)javascript
Redux是出了名的短小精悍(恩,這個形容很貼切),只有2kb大小,且沒有任何依賴。它將全部的髒活累活都交給了中間件去處理,本身保持着很好的純潔性。再加上redux做者在redux的源碼上,也附加了大量的註釋,所以redux的源碼讀起來仍是不算難的。java
先來看看redux的源碼結構,也就是src目錄下的代碼:react
其中utils是工具函數,主要是做爲輔助幾個核心API,所以不做討論。 (注:因爲篇幅的問題,下面代碼不少都刪除了官方註釋,和較長的warn)redux
index.js是redux的入口函數具體代碼以下:數組
import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
import warning from './utils/warning'
import __DO_NOT_USE__ActionTypes from './utils/actionTypes'
function isCrushed() {}
if (
process.env.NODE_ENV !== 'production' &&
typeof isCrushed.name === 'string' &&
isCrushed.name !== 'isCrushed'
) {
warning(
)
}
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
__DO_NOT_USE__ActionTypes
}
複製代碼
其中isCrushed函數是用於驗證在非生產環境下 Redux 是否被壓縮,若是被壓縮就會給開發者一個 warn 的提示。bash
在最後index.js 會暴露 createStore, combineReducers, bindActionCreators, applyMiddleware, compose 這幾個redux最主要的API以供你們使用。app
createStore函數接受三個參數:函數
下面就是creactStore的源碼,因爲總體源碼過長,且 subscribe 和 dispatch 函數也挺長的,因此就將 subscribe 和 dispatch 單獨提出來細講。工具
import $$observable from 'symbol-observable'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
export default function createStore(reducer, preloadedState, enhancer) {
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
// enhancer應該爲一個函數
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
//enhancer 接受 createStore 做爲參數,對 createStore 的能力進行加強,並返回加強後的 createStore 。
// 而後再將 reducer 和 preloadedState 做爲參數傳給加強後的 createStore ,最終獲得生成的 store
return enhancer(createStore)(reducer, preloadedState)
}
// reducer必須是函數
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
// 初始化參數
let currentReducer = reducer // 當前整個reducer
let currentState = preloadedState // 當前的state,也就是getState返回的值
let currentListeners = [] // 當前的訂閱store的監聽器
let nextListeners = currentListeners // 下一次的訂閱
let isDispatching = false // 是否處於 dispatch action 狀態中, 默認爲false
// 這個函數用於確保currentListeners 和 nextListeners 是不一樣的引用
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
// 返回state
function getState() {
if (isDispatching) {
throw new Error(
......
)
}
return currentState
}
// 添加訂閱
function subscribe(listener) {
......
}
}
// 分發action
function dispatch(action) {
......
}
//這個函數主要用於 reducer 的熱替換,用的少
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
// 替換reducer
currentReducer = nextReducer
// 從新進行初始化
dispatch({ type: ActionTypes.REPLACE })
}
// 沒有研究,暫且放着,它是不直接暴露給開發者的,提供了給其餘一些像觀察者模式庫的交互操做。
function observable() {
......
}
// 建立一個store時的默認state
// 用於填充初始的狀態樹
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
複製代碼
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
if (isDispatching) {
throw new Error(
......
)
}
let isSubscribed = true
// 若是 nextListeners 和 currentListeners 是一個引用,從新複製一個新的
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error(
.......
)
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
// 從nextListeners裏面刪除,會在下次dispatch生效
nextListeners.splice(index, 1)
}
}
複製代碼
有時候有些人會以爲store.subscribe用的不多,其實否則,是react-redux隱式的爲咱們幫咱們完成了這方面的工做。subscribe函數能夠給 store 的狀態添加訂閱監聽,一旦咱們調用了 dispatch來分發action ,全部的監聽函數就會執行。而 nextListeners 就是儲存當前監聽函數的列表,當調用 subscribe,傳入一個函數做爲參數時,就會給 nextListeners 列表 push 這個函數。同時調用 subscribe 函數會返回一個 unsubscribe 函數,用來解綁當前傳入的函數,同時在 subscribe 函數定義了一個 isSubscribed 標誌變量來判斷當前的訂閱是否已經被解綁,解綁的操做就是從 nextListeners 列表中刪除當前的監聽函數。學習
dispatch是redux中一個很是核心的方法,也是咱們在平常開發中最經常使用的方法之一。dispatch函數是用來觸發狀態改變的,他接受一個 action 對象做爲參數,而後 reducer 就能夠根據 action 的屬性以及當前 store 的狀態,來生成一個新的狀態,從而改變 store 的狀態;
function dispatch(action) {
// action 必須是一個對象
if (!isPlainObject(action)) {
throw new Error(
......
)
}
// type必需要有屬性,不能是undefined
if (typeof action.type === 'undefined') {
throw new Error(
......
)
}
// 禁止在reducers中進行dispatch,由於這樣作可能致使分發死循環,同時也增長了數據流動的複雜度
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
// 當前的狀態和 action 傳給當前的reducer,用於生成最新的 state
currentState = currentReducer(currentState, action)
} finally {
// 派發完畢
isDispatching = false
}
// 將nextListeners交給listeners
const listeners = (currentListeners = nextListeners)
// 在獲得新的狀態後,依次調用全部的監聽器,通知狀態的變動
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
複製代碼
其中 currentState = currentReducer(currentState, action);這裏的 currentReducer 是一個函數,他接受兩個參數:
而後返回計算出來的新的狀態。
compose 能夠接受一組函數參數,從右到左來組合多個函數,而後返回一個組合函數。它的源碼並不長,但設計的十分巧妙:
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
複製代碼
compose函數的做用其實其源碼的註釋裏講的很清楚了,好比下面這樣:
compose(funcA, funcB, funcC)
複製代碼
其實它與這樣是等價的:
compose(funcA(funcB(funcC())))
複製代碼
ompose 作的只是讓咱們在寫深度嵌套的函數時,避免了代碼的向右偏移。
applyMiddleware也是redux中很是重要的一個函數,設計的也很是巧妙,讓人歎爲觀止。
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
// 利用傳入的createStore和reducer和建立一個store
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)
}
// 讓每一個 middleware 帶着 middlewareAPI 這個參數分別執行一遍
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
複製代碼
經過上面的代碼,咱們能夠看出 applyMiddleware 是個三級柯里化的函數。它將陸續的得到三個參數:第一個是 middlewares 數組,第二個是 Redux 原生的 createStore,最後一個是 reducer,也就是上面的...args;
applyMiddleware 利用 createStore 和 reducer 建立了一個 store,而後 store 的 getState 方法和 dispatch 方法又分別被直接和間接地賦值給 middlewareAPI 變量。
其中這一句我感受是最核心的:
dispatch = compose(...chain)(store.dispatch)
複製代碼
我特地將compose與applyMiddleware放在一塊,就是爲了解釋這段代碼。所以上面那段核心代碼中,本質上就是這樣的(假設...chain有三個函數):
dispatch = f1(f2(f3(store.dispatch))))
複製代碼
combineReducers 這個輔助函數的做用就是,將一個由多個不一樣 reducer 函數做爲 value 的 object合併成一個最終的 reducer 函數,而後咱們就能夠對這個 reducer 調用 createStore 方法了。這在createStore的源碼的註釋中也有提到過。
而且合併後的 reducer 能夠調用各個子 reducer,並把它們返回的結果合併成一個 state 對象。 由 combineReducers() 返回的 state 對象,會將傳入的每一個 reducer 返回的 state 按其傳遞給 combineReducers() 時對應的 key 進行命名。
下面咱們來看源碼,下面的源碼刪除了一些的檢查判斷,只保留最主要的源碼:
export default function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
// 有效的 reducer 列表
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
const finalReducerKeys = Object.keys(finalReducers)
// 返回最終生成的 reducer
return function combination(state = {}, action) {
let hasChanged = false
//定義新的nextState
const nextState = {}
// 1,遍歷reducers對象中的有效key,
// 2,執行該key對應的value函數,即子reducer函數,並獲得對應的state對象
// 3,將新的子state掛到新的nextState對象上,而key不變
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
}
// 遍歷一遍看是否發生改變,發生改變了返回新的state,不然返回原先的state
return hasChanged ? nextState : state
}
}
複製代碼
bindActionCreators能夠把一個 value 爲不一樣 action creator 的對象,轉成擁有同名 key 的對象。同時使用 dispatch 對每一個 action creator 進行包裝,以即可以直接調用它們。 bindActionCreators函數並不經常使用(反正我尚未怎麼用過),唯一會使用到 bindActionCreators 的場景就是咱們須要把 action creator 往下傳到一個組件上,卻不想讓這個組件覺察到 Redux 的存在,而且不但願把 dispatch 或 Redux store 傳給它。
// 核心代碼,並經過apply將this綁定起來
function bindActionCreator(actionCreator, dispatch) {
return function() {
return dispatch(actionCreator.apply(this, arguments))
}
}
// 這個函數只是把actionCreators這個對象裏面包含的每個actionCreator按照原來的key的方式所有都封裝了一遍,核心代碼仍是上面的
export default function bindActionCreators(actionCreators, dispatch) {
// 若是actionCreators是一個函數,則說明只有一個actionCreator,就直接調用bindActionCreator
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
// 若是是actionCreator是對象或者null的話,就會報錯
if (typeof actionCreators !== 'object' || actionCreators === null) {
throw new Error(
... ...
}
// 遍歷對象,而後對每一個遍歷項的 actionCreator 生成函數,將函數按照原來的 key 值放到一個對象中,最後返回這個對象
const keys = Object.keys(actionCreators)
const boundActionCreators = {}
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
複製代碼
看一遍redux,感受設計十分巧秒,不愧是大佬的做品。此次看代碼只是初看,日後隨着本身學習的不斷深刻,還需多加研究,絕對還能獲得更多的體會。