做者: HerryLohtml
本文永久有效連接: https://github.com/AttemptWeb......前端
Redux是JavaScript狀態容器,提供可預測化的狀態管理。react
在實際開發中,常搭配React + React-redux使用。這表明了目前前端開發的一個基本理念,數據和視圖的分離。git
redux應運而生,固然還有其餘的一些狀態管理庫,如Flux、Elm等,固然,咱們這裏只對redux進行解析。github
建立redux的store對象,須要調用combineReducers和createStore函數,下面解釋不包含中間件。編程
const reducer = combineReducers({
home: homeNumber,
number: addNumber
})
const store = createStore(reducer)
// 暫時掛載在window下,下面會使用到
window.$reduxStore = store
複製代碼
首先調用combineReducers函數,將多個reducer函數做爲參數傳入,源碼以下:redux
// reducers便是傳入的參數對象
function combineReducers(reducers) {
// ......省略
return function combination(state = {}, action) {
let hasChanged = false
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
// finalReducerKeys 是傳入reducers對象的key值
const key = finalReducerKeys[i]
// finalReducers 等價於 reducers
const reducer = finalReducers[key]
const previousStateForKey = state[key]
// 運行reducer函數,返回一個state
// 核心:調用combination函數,實際就是循環調用傳入的reducer函數
const nextStateForKey = reducer(previousStateForKey, action)
nextState[key] = nextStateForKey
// hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
// hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length
// 返回state對象
return nextState
}
}
// 源碼地址:https://github.com/reduxjs/redux/blob/master/src/combineReducers.ts#L139
複製代碼
上面的代碼其實很是簡單,combineReducers函數運行,返回一個新的combination函數。combination函數的主要做用是返回一個掛載所有state的對象。 當combination函數被調用時,實際就是循環調用傳入的reducer函數,返回state對象。將combination函數做爲參數傳入到createStore函數中。數組
function createStore(reducer, preloadedState, enhancer) {
// reducer --> combination函數
let currentReducer = reducer
// 所有的state屬性,掛載在currentState上
let currentState = preloadedState
// 下面的中間件會用到
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
// 第二個參數是一個函數,沒有第三個參數的狀況
enhancer = preloadedState
// 將preloadedState重置
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
// 存在中間件時,將createStore傳入中間件函數,調用enhancer函數,return結束。
return enhancer(createStore)(reducer, preloadedState)
}
function dispatch(action) {
// currentReducer --> combination函數
currentState = currentReducer(currentState, action)
}
// 初始化調用dispatch,建立初始state
dispatch({ type: ActionTypes.INIT })
const store = ({
dispatch: dispatch,
subscribe,s
getState,
replaceReducer,
[$$observable]: observable
}
return store
}
// 源碼地址:https://github.com/reduxjs/redux/blob/master/src/createStore.ts#L60
複製代碼
reducer
就是傳入的combination函數,preloadedState是初始化的state(沒有太大的做用),enhancer是中間件,沒有第三個參數enhancer的狀況下,同時第二個參數preloadedState是一個函數,preloadedState被賦值給enhancer。bash
調用dispatch
函數初始化,currentReducer
便是傳入combination
函數,就向上文提到的,調用combination
函數實際就是循環調用reducer函數。全部的state對象,被掛載在內部變量currentState
上。存在中間件enhancer時,將createStore傳入中間件函數,調用enhancer函數,return結束,這個下文會繼續講到。前端框架
建立的store對象,暴露出的方法以下:
const store = ({
// 分發 action,這是觸發 state 變化的唯一途徑。
dispatch: dispatch as Dispatch<A>,
// 變化監聽器
subscribe,
// 獲取store下的 所有state
getState,
// 替換 store 當前用來計算 state 的 reducer
replaceReducer
}
return store
複製代碼
dispatch
函數觸發action,調用reducer函數,修改state。subscribe
函數能夠監聽變化state的變化。getState
函數獲取所有state。replaceReducer
函數替換用來計算state的reducer
函數。
經過combineReducers
函數合併reducer函數,返回一個新的函數combination
(這個函數負責循環遍歷運行reducer函數,返回所有state)。將這個新函數做爲參數傳入createStore
函數,函數內部經過dispatch,初始化運行傳入的combination
,state生成,返回store對象
最好把上面看懂以後,再看中間件部分!!下面對中間件進行分析:
redux-thunk
只是redux中間件的一種,也是比較常見的中間件。redux-thunk
庫容許你編寫與store交互的異步邏輯。
import thunkMiddleware from 'redux-thunk'
const reducer = combineReducers({
home: homeNumber,
number: addNumber
})
const store = createStore(
reducer,
applyMiddleware(
thunkMiddleware, // 異步支持
)
)
複製代碼
createStore函數支持三個參數,若是第二個參數preloadedState是一個函數,而沒有第三個參數enhancer的話,preloadedState會被賦值給enhancer。
下面會以redux-thunk
中間件做爲例子,下面就是thunkMiddleware函數的代碼:
// 部分轉爲ES5代碼,運行middleware函數會返回一個新的函數,以下:
return ({ dispatch, getState }) => {
// next實際就是傳入的dispatch
return function (next) {
return function (action) {
// redux-thunk核心
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
};
}
// 源碼地址:https://github.com/reduxjs/redux-thunk/blob/master/src/index.js
複製代碼
redux-thunk
庫內部源碼很是的簡單,github: redux-thunk 源碼
// 中間件調用
return enhancer(createStore)(reducer, preloadedState)
等價於
return applyMiddleware(
thunkMiddleware,
)(createStore)(reducer, preloadedState)
複製代碼
redux的中間件,從applyMiddleware函數開始,它主要的目的就是爲了處理store的dispatch函數
。
// 支持多箇中間件傳入
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, ...args) => {
// 建立 store
const store = createStore(reducer, ...args)
const middlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
// 遍歷運行中間件函數,將middlewareAPI做爲參數傳入
// middleware對應上面的redux-thunk庫核心代碼,固然也支持多箇中間件
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 核心:將全部中間件傳入到compose中,返回一個新的dispatch
dispatch = compose(...chain)(store.dispatch)
// 照常返回一個store對象,dispatch已經被處理過了
return {
...store,
dispatch
}
}
}
// 源碼地址:https://github.com/reduxjs/redux/blob/master/src/applyMiddleware.ts#L55
複製代碼
applyMiddleware
函數接收多個middlewares參數,返回一個store對象。經過createStore建立store對象,middlewareAPI對象掛載getState和dispatch,循環middlewares中間件,將middlewareAPI做爲參數傳入每一箇中間件。遍歷結束之後,拿到了一個包含全部中間件新返回函數的一個數組,將其賦值給變量chain。
// 遍歷以後chain的值,這裏只是拿redux-thunk庫做爲例子
// next 就是 dispatch
chain = [function (next) {
return function (action) {
if (typeof action === 'function') { // redux-thunk核心
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}, ...更多中間件]
複製代碼
// 數組chain 保存全部中間件新返回函數
dispatch = compose(...chain)(store.dispatch)
複製代碼
compose的主要做用就是運行全部中間件函數後,返回一個通過處理的dispatch函數。
// compose函數
return chain.reduce((a, b) =>{
return (...args)=> {
return a(b(...args))
}
}
複製代碼
chain是保存中間件函數的數組,具體的內部結構參見上面👆,下面來分析一下compose函數的調用邏輯。
// chain 類比爲 [fn1, fn2, fn3, fn4]
[fn1, fn2, fn3, fn4].reduce((a, b) =>{
return (...args)=> {
return a(b(...args))
}
}
複製代碼
調用過程以下:
循環 | a值 | b值 | 返回的值 |
---|---|---|---|
第一輪循環 | fn1 | fn2 | (...args)=> fn1(fn2(...args)) |
第二輪循環 | (...args)=> fn1(fn2(...args)) | fn3 | (...args)=> fn1(fn2(fn3(...args))) |
第三輪循環 | (...args)=> fn1(fn2(fn3(...args))) | fn4 | (...args)=> fn1(fn2(fn3(fn4(...args)))) |
通過 compose 處理過以後, 最後的返回值就是 (...args) => fn1(fn2(fn3(fn4(...args))))
,這個的arg就是 store.dispatch函數。最後將返回函數賦值給dispatch,就是咱們須要的dispatch函數了。而若是隻有一箇中間件的話,就會直接返回了。
applyMiddleware函數中間件的主要目的就是修改dispatch函數,返回通過中間件處理的新的dispatch函數
這裏的使用是不配合react-redux+react的;
window.$reduxStore = store
store.dispatch(action);
let { aState } = store.getState()
複製代碼
上面是直接將其掛載在window對象之下,這樣能夠配合任何前端框架使用,固然這樣確定是不優雅的,後面我再會專門講一篇配合react-redux使用的;
在這裏調用store.dispatch函數,實際就是再次調用循環遍歷調用reducer函數,更新以後被保存在createStore函數的內部變量currentState上。經過store.getState函數,返回currentState變量,便可獲得全部state。
內容有點多,須要總結一下
1.redux建立Store:經過combineReducers
函數合併reducer函數,返回一個新的函數combination
(這個函數負責循環遍歷運行reducer函數,返回所有state)。將這個新函數做爲參數傳入createStore
函數,函數內部經過dispatch,初始化運行傳入的combination
,state生成,返回store對象。
2.redux中間件:applyMiddleware函數中間件的主要目的就是修改dispatch函數,返回通過中間件處理的新的dispatch函數
3.redux使用:實際就是再次調用循環遍歷調用reducer函數,更新state
解析到這裏就結束了,redux真的很是函數化,滿滿的函數式編程的思想,很是的模塊化,具備很強的通用性,以爲很是贊👍👍
ps: 微信公衆號:Yopai,有興趣的能夠關注,每週不按期更新,分享能夠增長世界的快樂