redux簡介(二)源碼解析

寫在開頭

前置知識內(nèi)容,閉包高階函數(shù)检痰,函數(shù)式編程思想愤惰,redux核心概念苇经。

git clone https://github.com/reduxjs/redux.git

本文對應(yīng)redux版本為redux v4.0.1

一、結(jié)構(gòu)

redux源碼結(jié)構(gòu)

在redux的src目錄下可以清楚的看到redux的幾個核心文件和js工具函數(shù)宦言。通過閱讀index.js文件可以清楚的看到redux導(dǎo)出的5個核心方法

文件 功能
index redux的入口文件 用法
createStore 提供核心APIcreateStore根據(jù)reducer扇单,preState,applyMiddleware奠旺。創(chuàng)建并返回store
combineReducers 提供核心APIcombineReducers,用于合并拆分的reducer
bindActionCreators 提供核心APIbindActionCreators 可以簡化dispatch action的調(diào)用方法蜘澜。
applyMiddleware 提供核心APIapplyMiddleware
compose
actionTypes.js 內(nèi)置的action.type响疚。
isPlainObject.js 判斷是否是簡單對象鄙信。
warning.js 用于輸出警告信息。

二忿晕、工具文件

2.1 actionTypes

源碼截取

const randomString = () =>
  Math.random()
    .toString(36)
    .substring(7)
    .split('')
    .join('.');

const ActionTypes = {
  INIT: `@@redux/INIT${randomString()}`,
  REPLACE: `@@redux/REPLACE${randomString()}`,
  PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`
};

export default ActionTypes

actionTypes文件主要封裝了redux內(nèi)置的actionType装诡,其中ActionTypes.INIT主要用于初始化store所使用。REPLACE PROBE_UNKNOWN_ACTION為替換reducer的actionType。

2.2isPlainObject

源碼截取

export default function isPlainObject(obj) {
  if (typeof obj !== 'object' || obj === null) return false

  let proto = obj
  while (Object.getPrototypeOf(proto) !== null) {
    proto = Object.getPrototypeOf(proto)
  }

  return Object.getPrototypeOf(obj) === proto
}

本段函數(shù)主要用于對 reducer函數(shù)的action參數(shù)進(jìn)行校驗(yàn)鸦采。函數(shù)用于判斷一個對象是否是一個簡單對象宾巍,簡單對象是指直接使用對象字面量{}或者new Object()Object.create(null)所創(chuàng)建的對象渔伯。jQuerylodash等JavaScript函數(shù)均對此有所實(shí)現(xiàn).redux的老期版本使用了ladash的實(shí)現(xiàn)版本顶霞,后改為自己實(shí)現(xiàn)。

2.3warning

源碼截仍酆怠:

export default function warning(message) {
  if (typeof console !== 'undefined' && typeof console.error === 'function') {
    console.error(message)
  }
  try {
    // This error was thrown as a convenience so that if you enable
    // "break on all exceptions" in your console,
    // it would pause the execution at this line.
    throw new Error(message)
  } catch (e) {
  }
}

本函數(shù)主要用于對代碼執(zhí)行過程中所遇到的錯誤進(jìn)行統(tǒng)一處理确丢,在控制臺打印錯誤原因。使用throw new Error(message)是為了方便調(diào)試時中斷執(zhí)行

三吐限、核心文件

3.1 createStore.js

使用場景

// reducer是必傳的函數(shù)主要用于響應(yīng)action對store做處理鲜侥。
// preloadedState是一個可選對象,用于指定redux中store的默認(rèn)值诸典,使用場景比如說描函,服務(wù)端渲染是redux數(shù)據(jù)的注入,或者應(yīng)用程序頁面刷新時redux數(shù)據(jù)的保留狐粱。
// enhancer 用于增強(qiáng)redux的功能舀寓,比如處理異步,打印log等等
createStore(reducer, [preloadedState], enhancer)

creeateStore.js主要暴露了一個函數(shù)肌蜻,即createStore函數(shù)互墓。整個函數(shù)除去注釋,約為180行左右蒋搜。
下面將對此函數(shù)進(jìn)行分析篡撵。此函數(shù)的參數(shù)為(rducer,preloadState,enhancer)經(jīng)過處理返回一個store對象豆挽,store對象的值如下育谬。

{
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
}

createStore函數(shù)主要有5個變量用來存儲信息,通過變量名,我們可以很容易的知道每個變量的含義帮哈。createStore通過閉包將這些變量存儲起來膛檀,通過store的屬性來操作數(shù)據(jù)。

let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false

源代碼行數(shù)過多娘侍,在這里就不體現(xiàn)出來了咖刃,點(diǎn)擊這里查看

下面來逐行分析createStore函數(shù)的執(zhí)行過程
1.校驗(yàn)函數(shù)傳入的參數(shù)
函數(shù)執(zhí)行的第一步是函數(shù)校驗(yàn),如果校驗(yàn)未通過憾筏,直接拋出錯誤僵缺。
函數(shù)的第一個參數(shù)是必傳的函數(shù)(reducers),函數(shù)的額第二個參數(shù)是可選的除函數(shù)外的任意類型參數(shù)preloadState踩叭,參數(shù)的第三個參數(shù)是可選的函數(shù)enhancer(applyMiddleWare函數(shù)返回的函數(shù)組成的數(shù)組)磕潮。也可以傳遞兩個參數(shù)reducersenhancer翠胰。
函數(shù)校驗(yàn)了四個地方
第一步,由于函數(shù)可以支持多個中間件函數(shù)自脯,但是多個中間件必須要通過compose函數(shù)包裝之后在可以傳入之景。函數(shù)首先對此進(jìn)行了校驗(yàn)。判斷如果函數(shù)的第二個和第三個參數(shù)都為函數(shù)膏潮《凸罚或者函數(shù)的第三個參數(shù)和第四個參數(shù)都為函數(shù)。則可以猜測到焕参,開發(fā)者可能是為使用compose包裝多個中間件轻纪,所導(dǎo)致,所以此時函數(shù)會拋出錯誤信息叠纷。提示出當(dāng)前可能的錯誤原因刻帚。
第二步,對于函數(shù)只傳入reducersenhancer進(jìn)行了處理涩嚣。判斷傳入的第二個參數(shù)是函數(shù)崇众,并且沒有傳遞第三個參數(shù),此時將preloadState賦值給enhancer航厚。將preloadState置空顷歌。是實(shí)參與形參相對應(yīng)。
第三步幔睬,判斷enhancer是否存在眯漩,如果不是函數(shù),就拋出錯誤麻顶。如果是函數(shù)則直接返回enhancer(createStore)(reducer, preloadedState),這里可能不好理解坤塞,需要參照applyMiddleWare函數(shù)部分源碼。
第四步澈蚌,判斷傳入的reducer參數(shù)的類型是否是函數(shù),如果不是函數(shù)灼狰,拋出錯誤宛瞄。
到這里,函數(shù)參數(shù)的校驗(yàn)就結(jié)束了交胚。
2.定義函數(shù)內(nèi)的局部變量來存儲傳入的參數(shù)和后續(xù)會用到的狀態(tài)等信息

let currentReducer = reducer //傳入的reducer參數(shù)
let currentState = preloadedState //當(dāng)前store里的state
let currentListeners = []//當(dāng)前監(jiān)聽函數(shù)數(shù)組
let nextListeners = currentListeners //接下來的監(jiān)聽函數(shù)數(shù)組
let isDispatching = false //是否正在dispatch

3.定義六個函數(shù)來對來處理store的數(shù)據(jù)交互
第一個函數(shù):ensureCanMutateNextListeners

function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
        nextListeners = currentListeners.slice()
    }
}

此函數(shù)的功能較為簡單份汗,就不在說明
第二個函數(shù):getState

function getState() {
    if (isDispatching) {
        throw new Error('msg')
    }
    return currentState
}

此函數(shù)就是最終暴露出來的store.dispatch函數(shù),函數(shù)首先判斷當(dāng)前是不是處于dispatching狀態(tài)蝴簇。是的話直接拋出錯誤杯活。否則就將函數(shù)的局部變量current返回。
第三個函數(shù):subscribe

function subscribe(listener) {
    if (typeof listener !== 'function') {
        throw new Error('msg')
    }
    if (isDispatching) {
        throw new Error('msg')
    }
    let isSubscribed = true;
    ensureCanMutateNextListeners();
    nextListeners.push(listener);
    return function unsubscribe() {
        if (!isSubscribed) {
            return
        }
        if (isDispatching) {
            throw new Error('msg')
        }
        isSubscribed = false;
        ensureCanMutateNextListeners();
        const index = nextListeners.indexOf(listener);
        nextListeners.splice(index, 1)
    }
}

此函數(shù)也是最終暴露出來的store.subscribe函數(shù)熬词,此函數(shù)用于添加一個訂閱state變化的函數(shù)旁钧。傳入?yún)?shù)為函數(shù)吸重,如果傳入非函數(shù),或者正在dispatching時調(diào)用歪今,會拋出錯誤嚎幸。函數(shù)返回了一個函數(shù)用于取消訂閱。

接下來逐步分析此函數(shù)的執(zhí)行過程寄猩,首先參數(shù)和當(dāng)前狀態(tài)的校驗(yàn)嫉晶。然后定義局部變量標(biāo)記是否已經(jīng)訂閱并標(biāo)記為true,然后調(diào)用之前的ensureCanMutateNextListeners()函數(shù)田篇。以確保nextListeners與currentListeners相同替废。然后將傳入的listener函數(shù)添加到nextListeners數(shù)組。隨后返回一個新的函數(shù)用于取消訂閱泊柬。

在返回的新的函數(shù)中椎镣,首先做了狀態(tài)的判斷,如果正在dispatching彬呻,那么拋出錯誤衣陶,通過之前定義的是否訂閱標(biāo)記判斷,當(dāng)前的listener是否還在被訂閱闸氮。如果已經(jīng)取消了訂閱剪况,那么直接返回,(這里主要處理了取消訂閱函數(shù)可以被多次調(diào)用從而產(chǎn)生錯誤的情況蒲跨。這里使用了閉包)否則译断,將取消訂閱的標(biāo)記置為false。再此執(zhí)行ensureCanMutateNextListeners()或悲,原因同上孙咪。然后使用數(shù)組的indexOf方法在當(dāng)前的
nextListeners數(shù)組中找出對應(yīng)的索引。使用數(shù)組的splice方法將其刪除巡语。

第四個函數(shù):dispatch

function dispatch(action) {
    if (!isPlainObject(action)) {
        throw new Error('msg' )
    }

    if (typeof action.type === 'undefined') {
        throw new Error('msg')
    }

    if (isDispatching) {
        throw new Error('msg')
    }

    try {
        isDispatching = true;
        currentState = currentReducer(currentState, action)
    } finally {
        isDispatching = false
    }

    const listeners = (currentListeners = nextListeners);
    for (let i = 0; i < listeners.length; i++) {
        const listener = listeners[i];
        listener()
    }
    return action
}

這個函數(shù)也是最終暴露出來的store.dispatch方法翎蹈。主要用于觸發(fā)action以修改state,是非常重要的幾個函數(shù)之一男公。函數(shù)執(zhí)行過程中荤堪。首先校驗(yàn)參數(shù)action必須為普通javaScript對象,且action.type必須不能為undefined枢赔,并且當(dāng)前不能處于dispatching狀態(tài)澄阳。否則就會拋出錯誤。校驗(yàn)通過后踏拜。將isDispatching置為true碎赢,通過執(zhí)行currentState = currentReducer(currentState, action)更改state,此時便成功的觸發(fā)了一個action。在執(zhí)行完成之后速梗,將isDispatching置為false肮塞。隨后依次執(zhí)行訂閱此store的函數(shù)襟齿。代碼如下

const listeners = (currentListeners = nextListeners);
for (let i = 0; i < listeners.length; i++) {
    const listener = listeners[i];
    listener()
}

最后將action參數(shù)返回
第五個函數(shù):replaceReducer

function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
        throw new Error('Expected the nextReducer to be a function.')
    }
    currentReducer = nextReducer;
    dispatch({ type: ActionTypes.REPLACE })
}

此函數(shù)也是直接暴露出來的store.replaceReducer函數(shù),一般在實(shí)際開發(fā)中使用較少峦嗤。 主要用于動態(tài)加載reducer蕊唐。

第六個函數(shù):observable

function observable() {
    const outerSubscribe = subscribe;
    return {
        subscribe(observer) {
            if (typeof observer !== 'object' || observer === null) {
                throw new TypeError('Expected the observer to be an object.')
            }

            function observeState() {
                if (observer.next) {
                    observer.next(getState())
                }
            }

            observeState();
            const unsubscribe = outerSubscribe(observeState);
            return { unsubscribe }
        },

        [$$observable]() {
            return this
        }
    }
}

接下來createStore函數(shù)將直接返回已經(jīng)創(chuàng)建好的函數(shù)。

return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
}

3.2 combineReducers.js

combineReducers.js文件在去除注釋和錯誤提示的情況下烁设,代碼行數(shù)約為130行左右替梨。查看源碼。此js文件中主要有4個函數(shù)

函數(shù)名 作用 備注
getUndefinedStateErrorMessage 供redux內(nèi)部調(diào)用装黑,根絕action和key生成錯誤信息副瀑,例如reduce函數(shù)返回undefined時的情況
getUnexpectedStateShapeWarningMessage 供redux內(nèi)部調(diào)用,用于獲取警告信息恋谭,主要進(jìn)行了 reducer的判空糠睡,當(dāng)前的state是否為簡單對象,給state中存在而reducer中不存在的屬性添加緩存標(biāo)識疚颊,并返回警告信息狈孔。
assertReducerShape 檢測用于組合的reducer是否符合redux對頂?shù)膔educer
combineReducers 直接暴露出來,用于合并reducer

assertReducerShape材义,主要對于傳入的reducer數(shù)組進(jìn)行便利進(jìn)行檢驗(yàn)均抽,首先調(diào)用reducer(undefined,{ type: ActionTypes.INIT })以獲取initState,initState如果是undefined則拋出錯誤信息,提示必須返回初始state其掂,如果不想為這個reducer設(shè)置值油挥,要返回null而不是undefined。如果沒有出錯款熬,則調(diào)用reducer(undefined, { type: ActionTypes.PROBE_UNKNOWN_ACTION() }),通過未知的action深寥,來檢測reducer能否正確處理,即返回的值類型是否為undefined,如果是則拋出錯誤原因贤牛。

combineReducers惋鹅, 函數(shù)首先對傳入的reducers對象進(jìn)行遍歷,將結(jié)果賦值到局部變量finalReducers殉簸。如果不是正式環(huán)境闰集,那么對于為null的reducer進(jìn)行提示,如果是正式環(huán)境喂链,則忽略類型部位函數(shù)的reducer。然后調(diào)用assertReducerShape對finalReducers進(jìn)行類型校驗(yàn)妥泉,并存儲錯誤信息到局部變量shapeAssertionError椭微。然后反對一個合并完成的reducer函數(shù)。
在新返回的函數(shù)中盲链,首先判斷shapeAssertionError蝇率,如果存在錯誤就拋出迟杂,對于非正式環(huán)境,使用getUnexpectedStateShapeWarningMessage進(jìn)行校驗(yàn)本慕,并提醒錯誤排拷。定義一個標(biāo)記表示state是否已經(jīng)變化并置為false,定義下一個狀態(tài)的結(jié)果nextState,接下來遍歷執(zhí)行reducer锅尘。最終將nextState返回

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]
  const nextStateForKey = reducer(previousStateForKey, action)
  if (typeof nextStateForKey === 'undefined') {
    const errorMessage = getUndefinedStateErrorMessage(key, action)
    throw new Error(errorMessage)
  }
  nextState[key] = nextStateForKey
  hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state

3.3 applyMiddleware.js

源碼截取

import compose from './compose'

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);
        // 注意由于函數(shù)說引用類型所以此時middlewareAPI的dispatch函數(shù)也一同更改监氢。
        return {
            ...store,
            dispatch
        }
    }
}

applyMiddleware函數(shù)主要是與中間件有關(guān)的函數(shù),他允許我們在action到達(dá)reducer之前對action進(jìn)行加工處理藤违。

使用

// createStore中對于中間件的調(diào)用
return enhancer(createStore)(reducer, preloadedState)
//applyMiddleware函數(shù)的使用浪腐;
let store=createStore(reducer,preloadStste,applyMiddleware(thunk))
// redux-thunk源碼
function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

這里部分源碼設(shè)計(jì)到了高階函數(shù),初次閱讀可能不太好理解顿乒,所以將于applyMiddleware有關(guān)的內(nèi)容均列在了上面议街。由此來梳理applyMiddleware函數(shù)的工作流程。

在創(chuàng)建store時璧榄,首先調(diào)用createStore方法特漩,第三個參數(shù)為applyMiddleware函數(shù)返回的新的函數(shù)(起名字為函數(shù)1,源碼中第一個箭頭函數(shù))骨杂。在createStore源碼中可以看到涂身,檢測到第三個參數(shù)數(shù)為函數(shù)時將會執(zhí)行此函數(shù)(函數(shù)1),并且把createStore函數(shù)作為函數(shù)1的參數(shù)腊脱。函數(shù)1的執(zhí)行會返回一個新的函數(shù)(函數(shù)2访得,源碼中第二個箭頭函數(shù))。通過createStore源碼可以看到陕凹,此時再次對函數(shù)二悍抑,進(jìn)行了執(zhí)行,傳入的參數(shù)時reducer和preloadState杜耙。此時便開始執(zhí)行applyMiddleware函數(shù)的主體部分搜骡。

在applyMiddleware中可以看到,首先根據(jù)傳入的reducer和preloadState創(chuàng)建了store佑女。然后定義了一個dispatch函數(shù)记靡。作用是執(zhí)行時的校驗(yàn),避免在執(zhí)行函數(shù)時dispaching团驱。然后創(chuàng)建了middlewareAPI對象供中間件函數(shù)使用摸吠。然后將middlewareAPI作為參數(shù),便利執(zhí)行中間件數(shù)組函數(shù)嚎花。將返回的結(jié)果存儲在chain數(shù)組寸痢。通過thunk函數(shù)的源碼使得我們可以了解到此時存儲在chain書中的的每一項(xiàng)仍然是一個函數(shù),函數(shù)接收的形參為next紊选。隨后將store.dispatch作為參數(shù)執(zhí)行chain數(shù)組中的每一個函數(shù)啼止,具體為首先執(zhí)行第一個函數(shù)道逗,將store.dispatch作為實(shí)參傳入。將其返回的結(jié)果作為第二個函數(shù)的參數(shù)献烦,以此類推滓窍,將最后結(jié)果賦值給dispatch。根絕thunk的源碼可以看到巩那,對于chain數(shù)組的每一項(xiàng)吏夯,執(zhí)行后仍然會返回一個新的函數(shù),這個新的函數(shù)的形參為action,這個新的函數(shù)恰恰就是我們要自己開發(fā)的中間件函數(shù)拢操。最后將store和dispatch返回锦亦。此時回到createStore源碼,可以看到令境,返回的恰恰就是最終創(chuàng)建的store杠园。

現(xiàn)在已經(jīng)梳理創(chuàng)建含有中間件的store的函數(shù)執(zhí)行過程。下面來分析一下store.dispatch這個過程舔庶。

接著上面的來說抛蚁,最終返回了dispatch函數(shù)并且作為了store的一個屬性。當(dāng)我們觸發(fā)action的時候惕橙,顯然使用的就是這個dispatch函數(shù)∏扑Γ現(xiàn)在我們來看看這個dispatch函數(shù)是什么樣子的。

由于之前使用了compose函數(shù)弥鹦,所以這部分呢可能不太容易理解肚逸,此時我們,假設(shè)兩個中間件函數(shù)第一個為上面所提到的thunk中間件彬坏,假設(shè)第二個為打印log的中間件,經(jīng)過map處理后的如下朦促。

const logger = store => next => action => {
  console.group(action.type)
  console.info('dispatching', action)
  let result = next(action)
  console.log('next state', store.getState())
  console.groupEnd(action.type)
  return result
}

const thunk = ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };

為了利于理解我們首先考慮只有一個中間件的情況,此時

dispatch=componse([logger(middlewareAPI)])(store.dispatch)

//等同于
dispatch=logger(middlewareAPI)(store.dispatch)
    
// 所以

store.dispatch(action)=logger(middlewareAPI)(store.dispatch)(action)
    
// 此時發(fā)現(xiàn)
logger(middlewareAPI)(store.dispatch)(action) 與定義時的剛好對應(yīng)

考慮有多個中間件的情況栓始。

dispatch=componse([logger(middlewareAPI),thunk(middlewareAPI)])(store.dispatch)

執(zhí)行流程分析

  1. 根據(jù)compose函數(shù)的源碼可以將上面表達(dá)式轉(zhuǎn)化為
dispatch=thunk(middlewareAPI)(logger((middlewareAPI)(store.dispatch)))
  1. 首先執(zhí)行l(wèi)ogger(middlewareAPI)(store.dispatch)函數(shù)务冕,函數(shù)返回一個類似于next=>action=>{} 的函數(shù)。執(zhí)行這個函數(shù)幻赚,傳入的參數(shù)next的值恰好是store,dispatch禀忆。
  2. 然后執(zhí)行thunk函數(shù),函數(shù)也返回一個類似于next=>action=>{} 的函數(shù)落恼。thunk()函數(shù)執(zhí)行時傳入的是logger函數(shù)返回的函數(shù)箩退。
  3. 多個中間件以此類似
  4. 當(dāng)?shù)阶詈笠粋€中間件時,返回類似于next=>action=>{}的函數(shù)佳谦。next參數(shù)是倒數(shù)第二個中間件返回的函數(shù)戴涝。最后一個函數(shù)返回的函數(shù)被賦值給了dispatch

總結(jié):通過閉包存儲了最新的store值。通過compose函數(shù),使得每個中間件的next參數(shù)指向其后面的中間件函數(shù)喊括。最后一個中間件指向store.dispatch。當(dāng)觸發(fā)action時矢棚,action會依次的經(jīng)過中間件的處理郑什。在每個中間件中可以通過store.getState()取得最新的state值,通過dispatch可以從第一個中間件觸發(fā)dispatch()蒲肋。通過調(diào)用next(action)觸發(fā)下一個中間件函數(shù)

 dispatch = compose(...chain)(store.dispatch) 

compose函數(shù)
函數(shù)傳入的數(shù)組的每一項(xiàng)是形如next=>action=>{}的函數(shù)蘑拯。
作用

  • 每一個函數(shù)的next參數(shù)是對他之后函數(shù)的返回值
  • 最后一個函數(shù)的action是store.dispatch
  • 最后的返回值是第一個函數(shù)的返回值,賦值給dispatch
  • 每次調(diào)用dispatch兜粘,就是調(diào)用第一個中間件
  • 在中間件函數(shù)內(nèi)調(diào)用next就是調(diào)用下一個中間件的(action)=>{}
  • 調(diào)用最后一個中間件的next申窘,會調(diào)用store.dispatch,更新state。

接著從applyMiddleware函數(shù)源碼compose部分開始分析孔轴。此時的dispatch函數(shù)如下

dispatch=componse([chainOne,chainTwo])(store.dispatch)

根據(jù)compose源碼剃法。

dispatch=chainTwo(chainOne(store.dispatch))

3.4 bindActionCreators.js

源碼截取

function bindActionCreator(actionCreator, dispatch) {
    return function () {
        return dispatch(actionCreator.apply(this, arguments))
    }
}

export default function bindActionCreators(actionCreators, dispatch) {
    if (typeof actionCreators === 'function') {
        return bindActionCreator(actionCreators, dispatch)
    }
    
    // 校驗(yàn)參數(shù)必須為對象
    if (typeof actionCreators !== 'object' || actionCreators === null) {
        throw new Error(
            `bindActionCreators expected an object or a function, instead received ${
                actionCreators === null ? 'null' : typeof actionCreators
                }. ` +
            `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
        )
    }
    
    const boundActionCreators = {}
    for (const key in actionCreators) {
        const actionCreator = actionCreators[key]
        if (typeof actionCreator === 'function') {
            boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
        }
    }
    return boundActionCreators
}

在redux中改變state只能通過store.dispatch(action)的方式,這樣迫使我們不得不將store.dispatch函數(shù)進(jìn)行逐層傳遞路鹰,這樣會增加一些無用的重復(fù)代碼贷洲。

這時就需要使用bindActionCreators來加工actionCreators函數(shù)。bindActionCreators(actionCreators, dispatch)返回一個與原函數(shù)/對象相同的新函數(shù)晋柱。因?yàn)檫^閉包保存了store.dispatch并且通過apply調(diào)整了this的指向优构。直接執(zhí)行返回的函數(shù)/對象的屬性便可以觸發(fā)數(shù)據(jù)的改變,使得我們不在需要將dispatch逐層傳遞雁竞,也使得我們可以像執(zhí)行普通函數(shù)一樣來觸發(fā)action钦椭。

3.5 compose.js

源碼截取

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)))
}

函數(shù)的作用為將多個函數(shù)組合成一個函數(shù)。
使得compose(f,g,h)(...arg)等同于(...arg)=>f(g(h(...arg)))
在源碼的applyMiddleware函數(shù)中使用了此函數(shù)碑诉。

四彪腔、總結(jié)

以上這些內(nèi)容就是我對于redux的部分理解。

redux簡單的說就是一個狀態(tài)管理工具联贩。也可以與除了react之外的其他框架組合使用漫仆,比如說Vue.js,當(dāng)然選擇Vuex對于Vue是更好的選擇。對于React來說redux也不是其唯一的狀態(tài)管理工具泪幌,除此之外也有dva盲厌,mobox

五、寫在最后

以上這些內(nèi)容就是我對于redux的部分理解祸泪。從開始學(xué)習(xí)至文章產(chǎn)出大約一周左右吗浩。由于個人能力有限,所以可能有很多的不足之處没隘。當(dāng)然這篇文章也會不斷的更新懂扼,完善。

預(yù)告下一篇不定時更新文章,redux有關(guān)部分框架的源碼解析阀湿,比如react-redux,react-saga,reacr-router-redux赶熟。
推薦下載源碼進(jìn)行閱讀學(xué)習(xí),相信會有很多的收獲陷嘴,加油

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末映砖,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子灾挨,更是在濱河造成了極大的恐慌邑退,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件劳澄,死亡現(xiàn)場離奇詭異地技,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)秒拔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門莫矗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事“獍” “怎么了勋磕?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么彬伦? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮伊诵,結(jié)果婚禮上单绑,老公的妹妹穿的比我還像新娘。我一直安慰自己曹宴,他們只是感情好搂橙,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著笛坦,像睡著了一般区转。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上版扩,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天废离,我揣著相機(jī)與錄音,去河邊找鬼礁芦。 笑死蜻韭,一個胖子當(dāng)著我的面吹牛悼尾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肖方,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼闺魏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了俯画?” 一聲冷哼從身側(cè)響起舷胜,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎活翩,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體翻伺,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡材泄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了吨岭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拉宗。...
    茶點(diǎn)故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖辣辫,靈堂內(nèi)的尸體忽然破棺而出旦事,到底是詐尸還是另有隱情,我是刑警寧澤急灭,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布姐浮,位于F島的核電站,受9級特大地震影響葬馋,放射性物質(zhì)發(fā)生泄漏卖鲤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一畴嘶、第九天 我趴在偏房一處隱蔽的房頂上張望蛋逾。 院中可真熱鬧,春花似錦窗悯、人聲如沸区匣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽亏钩。三九已至,卻和暖如春欺旧,著一層夾襖步出監(jiān)牢的瞬間铸屉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工切端, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留彻坛,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像昌屉,于是被迫代替她去往敵國和親钙蒙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容