redux做爲前端狀體管理中最亮眼的那個仔,很是有必要弄清楚他的原理。本文將從源碼結合實踐一塊兒來從新認識redux。純乾貨分享!!!javascript
redux相對來說是相對比較複雜的狀態管理工具。實現一個全局狀態管理工具,經過一個全局變量和一些方法,便可實現的東西,那麼爲何redux須要提出action,store,dispatch,reducer等一系列概念?提出這些概念的做用或者說動機是什麼?但願讀者能從這篇文章中深刻理解這些概念存在的價值和意義。前端
export const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
);
複製代碼
咱們常常看到這段代碼,本文將從以createStore做爲入口順藤摸瓜帶你認識整個框架。下面源碼是v3.7.2版本的代碼。java
首先來看createStore函數源碼, 爲了方便理解和閱讀省略了不少無關的代碼,你們在閱讀的時候能夠摺疊起來看。react
export default function createStore(reducer, preloadedState, enhancer) {
// 若是隻有兩個參數,而且第二個參數是函數的話,將會傳遞給enhancer
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
// 省略一堆判斷邏輯
return enhancer(createStore)(reducer, preloadedState)
}
// 一堆方法定義
dispatch({ type: ActionTypes.INIT });
return {
dispatch, // 重點講述
subscribe, // 重點講述
getState, // 返回state的方法
replaceReducer, // 高級用法,目的在於分包時,動態更換reducer
[$$observable]: observable
}
}
複製代碼
function myEnhancer(createStore){
return (reducer, preloadedState, enhancer) => {
//建立store以前, do someSting
const store = createStore(reducer, preloadedState, enhancer)
//store以後, do something
return store;
}
}
複製代碼
dispatch是咱們的重頭戲,後面仍是介紹他,咱們先看下,當咱們dispatch({ type: 'INCREACE', payload: 1})會發生些什麼呢。redux
function dispatch(action) {
// 各類檢查acton類型
try {
isDispatching = true
// currentState是原來的state
// currentReducer就是一開始createStore時傳入的reducer
currentState = currentReducer(currentState, action)
// reducer以後返回的是更新後的新的state
} finally {
isDispatching = false
}
// 更新監聽者函數
const listeners = currentListeners = nextListeners
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
複製代碼
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
text: action.text,
completed: false
}
]
case 'COMPLETE_TODO':
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: true
})
}
return todo
})
default:
return state
}
}
複製代碼
subscribe是一個簡單的監聽者模式,該函數主要是收集監聽者。源碼很簡單以下bash
function subscribe(listener) {
// 檢查listener類型
let isSubscribed = true
ensureCanMutateNextListeners()
// 該函數會複製一份currentListeners
// 保障更新期間其餘listener不受影響
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
// 省略部分錯誤檢查
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
// 下次運行時 currentListeners會從新從nextListeners中取,能夠看dispatch的代碼
// 做者這樣作的目的主要是爲了防止dispatch執行期間發生subscribe或者unsubscribe引起異常錯誤
}
}
複製代碼
到這裏,整個redux的核心功能就介紹的差很少了,可是redux的威力並無體現出來,接下來咱們將介紹redux的擴展功能中間件。閉包
該函數是一個enhancer函數,由redux實現提供, 用來嵌入中間件,也是咱們常用的一個工具函數。架構
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args)
// 特別注意這個dispatch是使用let賦值的
// 這個預約義是爲了防止用戶提早使用,此時沒法觸發其餘中間件
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)
// 這個dispatch方法不能在next函數前使用
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
複製代碼
function loggerMiddleware({getState, dispatch}){ // 這部分對應的是middleware(middlewareAPI)
// 這塊區域不能使用dispatch函數,不然會拋出錯誤!!
return next => action => {
console.time(action.type);
const result = next(action);
// result 對象是一個action類型的對象,若是中間件未修改過該值,則全等,通常來說,action不該該被修改
console.timeEnd(action.type);
return result; // 將會傳入下一個中間中
}
}
複製代碼
在書寫中間件的時候,咱們發現內部閉包了多個函數,若是部分函數採用async等方式的話,就能夠實現異步操做,解決反作用的問題,redux-thunk正是借用這種方式實現,感興趣的同窗能夠學習下,代碼只有14行,這裏就不展開討論了。app
compose是一個函數構造器,返回一個新的函數。相似數學中的函數f(x),g(x),h(x)複合爲f(g(h(x)))。上一個函數的輸出做爲下一個函數的輸入。框架
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)))
}
複製代碼
function couponA(next) {
if(next >= 500){
return next - 50;
}
return x;
}
function couponB(next){
return next * 0.9;
}
const discount = compose(couponA, couponB);
discount(1000); // 850
複製代碼
當參數是一個值的時候,沒法實現迴旋鏢的形式。上述例子實際上是一個簡單的職責鏈模式,感興趣的能夠深刻挖掘,在電商打折規則中特別實用
這是一個工具函數,能夠將多個reducer聚合起來,返回值是一個reducer(這是一個函數)
// reducers是一個
export default function combineReducers(reducers) {
// 省略對reducers作了一堆檢查
// 下面這句是爲了好理解,我杜撰的,非真實源碼
const finalReducers = {...reducers}
const finalReducerKeys = Object.keys(finalReducers);
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]
// 這裏以key劃分命名空間,previousStateForKey爲指定key下的state
const nextStateForKey = reducer(previousStateForKey, action)
if (typeof nextStateForKey === 'undefined') {
// 每一個reducer都應該有返回值
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
}
複製代碼
該函數是redux提供的一個工具函數,首先要弄清楚action和actionCreator的關係。action是一個普通對象,actionCreator是一個構造action對象的函數
bindActionCreator的目的是將actionCreator與dispatch結合構造出一個可以直接觸發一系列變化的Action方法 bindActionCreators就是將多個actionCreator轉化爲Action方法
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}
export default function bindActionCreators(actionCreators, dispatch) {
// 省略一系列檢查
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
}
複製代碼
在實踐中,結合reat-redux的connect的第二個參數mapDispatchToProps爲例,展現actionCreators轉化爲能夠直接運行的方法。
const actionCreators = {
increase: (payload) => ({ type: 'INCREASE', payload }),
decrease: (payload) => ({ type: 'DECREASE', payload })
}
@connect(
state => state,
dispatch => ({
actions: boundActionCreators(actionCreators, dispatch)
})
)
class Counter {
render(){
<div>
<button onClick={() => this.props.actions.increase(1)}>increase</button>
<button onClick={() => this.props.actions.decrease(1)}>decrease</button>
</div>
}
}
複製代碼