Rudex源碼剖析

Redux官方代碼庫(kù)提供了以下幾個(gè)模塊文件:

applyMiddleware.js
bindActionCreators.js
combineReducers.js
compose.js
createStore.js

compose.js

/**
 * Composes single-argument functions from right to left. The rightmost
 * function can take multiple arguments as it provides the signature for
 * the resulting composite function.
 *
 * @param {...Function} funcs The functions to compose.
 * @returns {Function} A function obtained by composing the argument functions
 * from right to left. For example, compose(f, g, h) is identical to doing
 * (...args) => f(g(h(...args))).
 */
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)))
} 

以上代碼很好理解兼搏,當(dāng)compose無(wú)參數(shù)時(shí),返回一個(gè)空函數(shù),參數(shù)為唯一函數(shù)時(shí),直接將這個(gè)函數(shù)作為返回值,重點(diǎn)在于最后一部分:
return funcs.reduce((a, b) => (...args) => a(b(...args)))

對(duì)多個(gè)參數(shù)組合成的函數(shù)數(shù)組進(jìn)行reduce操作杭跪,其實(shí)以上代碼等同于:
return funcs.reduceRight((composed, f) => f(composed));

相當(dāng)于對(duì)數(shù)組內(nèi)的所有函數(shù),從右至左,將前一個(gè)函數(shù)作為后一個(gè)函數(shù)的入口參數(shù)依次返回教硫,比如compose(fn1,fn2,fn3)最后返回的結(jié)果應(yīng)該是這樣子的:
fn1(fn2(fn3))

bindActionCreators.js

import warning from'./utils/warning'

function bindActionCreator (actionCreator, dispatch) {
    return (...args) => dispatch(actionCreator(...args))
}

export default function bindActionCreators (actionCreators, dispatch) {
    if (typeof actionCreators ==='function') {
        return bindActionCreator(actionCreators, dispatch)
    }
    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 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)
        } else {
            warning(`bindActionCreators expected a function actionCreator for key '${key}', instead received type '${typeof actionCreator}'.`)
        }
    }
    return boundActionCreators
}

對(duì)于單個(gè)actionCreator,代碼很簡(jiǎn)單辆布,直接返回一個(gè)被dispatch包裹過(guò)的action而已瞬矩,對(duì)于多個(gè)actionCreators,如果入口參數(shù)是一個(gè)function谚殊,說(shuō)明只提供了一個(gè)actionCreator丧鸯,直接調(diào)用bindActionCreator(actionCreators,dispatch),對(duì)于以對(duì)象形式輸入的多個(gè)actionCreators嫩絮,對(duì)其遍歷輸出每一個(gè)bindActionCreator(actionCreators,dispatch)并封裝在具有同名鍵值的boundActionCreators對(duì)象中丛肢,這樣在我們需要調(diào)用action的地方直接boundActionCreators[actionCreate定義名]就可以了。

createStore.js

//用于校驗(yàn)是否是純對(duì)象
import isPlainObject from'lodash/isPlainObject'
//內(nèi)部私有屬性剿干,暫時(shí)不做擴(kuò)展
import $$observable from'symbol-observable'

//內(nèi)部action,用于調(diào)用所有reducers生成初始state
export const ActionTypes ={INIT:'@@redux/INIT'}
export default function createStore(reducer, preloadedState, enhancer) {
    if (typeof preloadedState ==='function' && typeof enhancer ==='undefined') {
        enhancer = preloadedState
        preloadedState = undefined
    }
    if (typeof enhancer !=='undefined') {
        if (typeof enhancer !=='function') {
            throw new Error('Expected the enhancer to be a function.')
        }
        //函數(shù)柯里化蜂怎,enhancer提供增強(qiáng)版(中間件擴(kuò)展)的store
        return enhancer(createStore)(reducer, preloadedState) 
    }
    //reducer必須是一個(gè)function
    if (typeof reducer !=='function') {
        throw new Error('Expected the reducer to be a function.')
    }
    //store內(nèi)部私有變量(外部無(wú)法直接訪問(wèn))
    let currentReducer = reducer
    let currentState = preloadedState
    let currentListeners = []
    let nextListeners = currentListeners
    let isDispatching = false
    //為下一階段監(jiān)聽(tīng)器快照提供備份
    function ensureCanMutateNextListeners () {
        if (nextListeners === currentListeners) {
            nextListeners = currentListeners.slice()
        }
    }

    //獲取最新state
    function getState() {
        return currentState
    }

    //用于訂閱state的更新
    function subscribe(listener) {
        if (typeof listener !=='function') {
            throw new Error('Expected listener to be a function.')
        }
        //保證只有第一次執(zhí)行unsubscribe()才是有效的,只取消注冊(cè)當(dāng)前l(fā)istener
        let isSubscribed =true
        //為每次訂閱提供快照備份nextListeners置尔,主要防止在遍歷執(zhí)行currentListeners回調(diào)
        //過(guò)程中觸發(fā)了訂閱/取消訂閱功能杠步,若直接更新currentListeners將造成當(dāng)前循環(huán)體邏輯混亂
        //因此所有訂閱/取消訂閱的listeners都是在nextListeners中存儲(chǔ)的,并不會(huì)影響當(dāng)前的dispatch(action)
        ensureCanMutateNextListeners()
        nextListeners.push(listener)
        //返回一個(gè)取消訂閱的函數(shù)
        return function unsubscribe() {
            //保證當(dāng)前l(fā)istener只被取消注冊(cè)一次
            if (!isSubscribed) { return }
            isSubscribed =false
            ensureCanMutateNextListeners()
            const index = nextListeners.indexOf(listener)
            nextListeners.splice(index,1)
        }
    }

    function dispatch(action) {
        //保證dispatch是個(gè)純對(duì)象榜轿,即字面量對(duì)象或Object創(chuàng)建的對(duì)象
        //這是因?yàn)樵及娴膁ispatch只支持同步action幽歼,約定的格式是純對(duì)象
        //可以使用中間件來(lái)dispatch擴(kuò)展功能,增加action的類型種類
        if (!isPlainObject(action)) {
            throw new Error('Actions must be plain objects. '+'Use custom middleware for async actions.')
        }
        //action必須要有key為type的動(dòng)作類型
        if (typeof action.type ==='undefined') {
            throw new Error('Actions may not have an undefined "type" property. '+'Have you misspelled a constant?')
        }
        //判斷在執(zhí)行dispatch的過(guò)程中是否已存在dispatch的執(zhí)行流
        //保證dispatch中對(duì)應(yīng)的reducer不允許有其他dispatch操作
        if (isDispatching) {
            throw new Error('Reducers may not dispatch actions.')
        }
        try {
            //根據(jù)提供的action谬盐,執(zhí)行根reducer從而更新整顆狀態(tài)樹(shù)
            isDispatching = true
            currentState = currentReducer(currentState, action)
        } finally {
            isDispatching = false
        }
        //通知所有之前通過(guò)subscribe訂閱state更新的回調(diào)listener
        const listeners = currentListeners = nextListeners
        for(let i =0; i < listeners.length; i++) {
            const listener = listeners[i]listener()
        }
        return action
    }


    //替換當(dāng)前reducers甸私,如從其他文件引入了新的reducers進(jìn)行熱加載
    function replaceReducer (nextReducer) {
        if (typeof nextReducer !=='function') {
            throw new Error('Expected the nextReducer to be a function.')
        }
    }

    function observable () {
        const outerSubscribe = subscribe
        return {
            subscribe (observer) {
                if (typeof observer !=='object') {
                    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
            }
        }
    }

    dispatch({ type: ActionTypes.INIT })
    return {
        dispatch,
        subscribe,
        getState,
        replaceReducer,
        [$$observable]: observable
    }
}

一眼望去,還是有些懵逼的飞傀,但如果我們把它劃分為以下三個(gè)部分分別理解或許就簡(jiǎn)單多了皇型。

  • 入口參數(shù):reducer、preloadState砸烦、enhancer
  • 內(nèi)部變量:currentReducer弃鸦、currentState、currentListeners幢痘、nextListeners唬格、isDispatching
  • 輸出:dispatch、subscribe、getState西轩、replaceReducer
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末员舵,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子藕畔,更是在濱河造成了極大的恐慌马僻,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件注服,死亡現(xiàn)場(chǎng)離奇詭異韭邓,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)溶弟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門女淑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人辜御,你說(shuō)我怎么就攤上這事鸭你。” “怎么了擒权?”我有些...
    開(kāi)封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵袱巨,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我碳抄,道長(zhǎng)愉老,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任剖效,我火速辦了婚禮嫉入,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘璧尸。我一直安慰自己咒林,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布爷光。 她就那樣靜靜地躺著映九,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瞎颗。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天捌议,我揣著相機(jī)與錄音哼拔,去河邊找鬼。 笑死瓣颅,一個(gè)胖子當(dāng)著我的面吹牛倦逐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播宫补,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼檬姥,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼曾我!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起健民,我...
    開(kāi)封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤抒巢,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后秉犹,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蛉谜,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年崇堵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了型诚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鸳劳,死狀恐怖狰贯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情赏廓,我是刑警寧澤涵紊,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站楚昭,受9級(jí)特大地震影響栖袋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜抚太,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一塘幅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧尿贫,春花似錦电媳、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至又谋,卻和暖如春拼缝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背彰亥。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工咧七, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人任斋。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓继阻,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瘟檩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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

  • export const ActionTypes = {INIT:'@@redux/INIT'} // 生成一個(gè)s...
    jiandan5850閱讀 477評(píng)論 0 0
  • 引言 首先我是一個(gè)菜雞抹缕,對(duì)redux的掌握還只是停留在能使用層面,這里只是記錄一下我的這個(gè)react博客的redu...
    ape_caesar閱讀 574評(píng)論 2 0
  • 用法 為了對(duì)中間件有一個(gè)整體的認(rèn)識(shí)墨辛,先從用法開(kāi)始分析卓研。調(diào)用中間件的代碼如下: 源碼 createStore.js#...
    黃子毅閱讀 9,577評(píng)論 4 19
  • compose是函數(shù)式編程中使用較多的一種寫法, 它把邏輯解耦在各個(gè)函數(shù)中,通過(guò)compose的方式組合函數(shù), 將...
    LynnicKwan閱讀 290評(píng)論 0 0
  • Source Time 為了便于理解,我將源碼中的箭頭函數(shù)全都改為具名函數(shù)(以fn加上數(shù)字標(biāo)記)背蟆,以便于對(duì)照分析:...
    Xiaobo2020閱讀 641評(píng)論 0 0