redux 源碼淺析

redux 源碼淺析

redux 版本號: "redux": "4.0.5"

redux 做爲一個十分經常使用的狀態容器庫, 你們都應該見識過, 他很小巧, 只有 2kb, 可是珍貴的是他的 reducerdispatch 這種思想方式react

在閱讀此文以前, 先了解/使用 redux 相關知識點, 才能更好地閱讀本文git

入口文件

入口是在 redux/src/index.js 中, 在入口文件中只作了一件事件, 就是引入文件, 集中導出
如今咱們根據他導出的方法, 來進行分析github

createStore

這個是 redux 最主要的 APIredux

使用

搭配這使用方法一塊兒, 能夠更好的瀏覽源碼segmentfault

createStore(reducer, [preloadedState], [enhancer])api

他的主要功能就是建立一個 store, 將 reducer 轉換到 store數組

參數

一共可接受三個參數:promise

  1. reducer (函數): 一個返回下一個狀態樹的還原函數,給定當前狀態樹和一個要處理的動做。
  2. [preloadedState] (任意值): 初始值, 能夠是來自於 storage 中的; 若是你用combinedReducers產生了reducer,這必須是一個普通對象,其類型與傳遞給它的鍵相同。
    也能夠自由地傳遞任何你的reducer可以理解的東西。
  3. [enhancer] (函數): store 的加強器, 能夠選擇性的加強, 用代碼來講就是 enhancer(createStore)(reducer, preloadedState), enhancer
    接受的參數就是 createStore, 一樣地他也須要 return 一個相似於 createStore 的結果, 也就是說, 只有咱們返回的是 一個像 createStore 的東西,
    他的具體實現咱們就能夠有不少微調 這裏附上一篇探討 enhancerapplyMiddleware 的文章 https://juejin.cn/post/684490...
// 簡單的例子:

function counterReducer(state, action) {
    switch (action.type) {
        case 'counter/incremented':
            return {value: state.value + 1}
        case 'counter/decremented':
            return {value: state.value - 1}
        default:
            return state
    }
}


let store = createStore(counterReducer, {
    value: 12345
})

store

createStore 返回的固然是一個 store, 他有本身的 apiapp

getState

返回應用程序的當前狀態樹異步

const state = store.getState()

dispatch(action)

這個其實不用我多說, 會 redux 的都應該知道這個

store.dispatch({type: 'counter/incremented'})

subscribe(listener)

添加一個監聽器, 每當 action dispatch 的時候, 都會調用 listener, 在 listener 中可使用 getState 來獲取當前的狀態樹

const unsubscribe = store.subscribe(() => {
    console.log('listener run')
    const current = store.getState()
    if (current.value === 12350) {
        unsubscribe()
    }
})

展現一個場景, 監聽事件, 當達到某個條件以後, 解除監聽事件

replaceReducer(nextReducer)

使用一個 reducer 替換當前的 reducer,對於 reducers 實現動態加載,也能夠爲 Redux 實現熱重載機制

源碼解析

createStore 文件是在 redux/src/createStore.js 中, 他接受的參數就是上面咱們說的那三個, 返回的也就是 store

首先是一段參數的判斷, 以及 enhancer 的返回

// 爲了適配 createStore(reducer, 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.')
    }
    // enhancer 的使用場景
    return enhancer(createStore)(reducer, preloadedState)
}

接下來定義一些變量和函數

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


// 若是相等 , 作了一層淺拷貝  將 currentListeners 同步到 nextListeners 中
// 避免相互影響
function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
        nextListeners = currentListeners.slice()
    }
}

store.getState

function getState() {
    // isDispatching 默認爲 false, 表示當前 store 是否正在 dispatch
    if (isDispatching) {
        throw new Error('//...')
    }

    // 直接返回當前 state , 默認爲入參 preloadedState
    return currentState
}

store.subscribe

// 忽略了錯誤判斷
function subscribe(listener) {
    let isSubscribed = true

    // 同步 nextListeners , currentListeners
    ensureCanMutateNextListeners()

    // 將 listener 加入 nextListeners 
    nextListeners.push(listener)

    // 返回解除監聽函數
    return function unsubscribe() {
        if (!isSubscribed) {
            // 若是 isSubscribed 已經爲 false 了 則 return
            // 狀況 1, 已經執行過unsubscribe了一次
            return
        }

        // flag
        isSubscribed = false

        // 同步 nextListeners , currentListeners
        ensureCanMutateNextListeners()
        const index = nextListeners.indexOf(listener)
        nextListeners.splice(index, 1)
        // 搜索 監聽器, 刪除
        currentListeners = null
    }
}

store.dispatch

function dispatch(action) {
    // 省略了 action 的 錯誤拋出
    // 總結:  action  必須是一個 Object  且 action.type 必須有值存在

    // 若是當前正在 isDispatching 則拋出 錯誤(通常來講不存在

    try {
        isDispatching = true
        // 執行 reducer, 須要注意的是 currentReducer 不能爲異步函數
        currentState = currentReducer(currentState, action)
    } finally {
        isDispatching = false
    }

    //  將 nextListeners 賦值給 currentListeners 執行 nextListeners 裏面的監聽器
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
        const listener = listeners[i]
        listener()
    }

    // 返回 action
    return action
}

store.replaceReducer

function replaceReducer(nextReducer) {
    // 若是 nextReducer 不是函數則拋出錯誤

    // 直接替換
    currentReducer = nextReducer

    // 相似 ActionTypes.INIT.  替換值
    dispatch({type: ActionTypes.REPLACE})
}

store.observable

還有一個額外的 observable 對象:

// 一個 Symbol.observable 的 polyfill
import $$observable from 'symbol-observable'

function observable() {
    // subscribe 就是 store.subscribe
    const outerSubscribe = subscribe
    return {
        subscribe(observer) {
            // 若是 observer 不是對象或者爲 null 則拋出錯誤

            function observeState() {
                if (observer.next) {
                    // next 的入參爲 固然 reducer 的值
                    observer.next(getState())
                }
            }

            observeState()

            // 添加了監聽
            const unsubscribe = outerSubscribe(observeState)
            return {unsubscribe}
        },

        // 獲取到當前 對象, $$observable 值是一個 symbol
        [$$observable]() {
            return this
        }
    }
}

這裏使用了 tc39 裏未上線的標準代碼 Symbol.observable, 若是你使用或者瞭解過 rxjs, 那麼這個對於你來講就是很簡單的, 若是不熟悉,
能夠看看這篇文章: https://juejin.cn/post/684490...

剩餘代碼

function createStore() {
    // 省略

    // 初始化了下值
    dispatch({type: ActionTypes.INIT})

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

combineReducers

使用

// 能夠接受多個 reducer, 實現一種 module 的功能
rootReducer = combineReducers({potato: potatoReducer, tomato: tomatoReducer})


// 返回值
{
    potato: {
        // 某些屬性
    }
,
    tomato: {
        // 某些屬性
    }
}


const store = createStore(rootReducer, {
    potato: {
        // 初始值
    }
})

有一點須要注意的是, reducer 都是須要默認值的,如:

function counterReducer(state = {value: 0}, action) {
    //...
}

源碼解析

combineReducers

先看 combineReducers 執行以後產生了什麼

function combineReducers(reducers) {
    // 第一步是獲取 key, 他是一個數組
    const reducerKeys = Object.keys(reducers)
    const finalReducers = {}

    // 遍歷 reducers, 賦值到 finalReducers 中, 確保 reducer 是一個函數, 不是函數則過濾
    for (let i = 0; i < reducerKeys.length; i++) {
        const key = reducerKeys[i]

        // 省略 reducers[key] 若是是 undefined 拋出錯誤

        if (typeof reducers[key] === 'function') {
            finalReducers[key] = reducers[key]
        }
    }

    // finalReducerKeys 通常來講是和 reducerKeys 相同的
    const finalReducerKeys = Object.keys(finalReducers)

    //定義了兩個遍歷
    let unexpectedKeyCache
    let shapeAssertionError

    try {
        // 此函數後面會詳細講述
        // 答題做用就是確認 finalReducers 中都是有初始值的
        assertReducerShape(finalReducers)
    } catch (e) {
        shapeAssertionError = e
    }
    //...
}

再看他又返回了什麼(記住結果必然也是一個 reducer)

function combineReducers(reducers) {

    //...


    return function combination(state = {}, action) {
        // 若是 assertReducerShape 出錯則拋出錯誤
        if (shapeAssertionError) {
            throw shapeAssertionError
        }

        // 忽略非 production 代碼

        // 預先定義一些變量
        let hasChanged = false
        const nextState = {}

        // 循環 finalReducerKeys 
        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) // 經過 reducer 再次生成值

            // 若是 nextStateForKey === undefined 則再次拋出異常

            // 給 nextState 賦值
            nextState[key] = nextStateForKey

            // 判斷是否改變 (初始值是 false)  判斷簡單的使用 !== 來比較
            // 若是已經爲 true   就一直爲 true 了
            hasChanged = hasChanged || nextStateForKey !== previousStateForKey
        }

        // 循環後再次對 true 作出判斷
        // 是否少了 reducer 而形成誤判
        hasChanged =
            hasChanged || finalReducerKeys.length !== Object.keys(state).length

        // 若是改變了 返回新值, 不然返回舊值
        return hasChanged ? nextState : state
    }
}

combineReducers 基本就是上述兩個函數的結合, 經過循環遍歷全部的 reducer 計算出值

assertReducerShape

function assertReducerShape(reducers) {
    Object.keys(reducers).forEach(key => {
        // 遍歷參數裏的 reducer
        const reducer = reducers[key]

        //執行初始操做 產生初始值都有初始值
        const initialState = reducer(undefined, {type: ActionTypes.INIT})

        //...   若是 initialState 是 undefined 則拋出錯誤


        // 若是 reducer 執行未知操做  返回的是 undefined 則拋出錯誤
        // 情景: 當前 reducer 使用了 ActionTypes.INIT 來產生值, 這可以經過上一步
        // 但在這一步就會被檢測出來
        if (
            typeof reducer(undefined, {
                type: ActionTypes.PROBE_UNKNOWN_ACTION()
            }) === 'undefined'
        ) {
            //... 拋出錯誤
        }
    })
}

這裏咱們能夠知道一點, 全部 reducer 咱們都必需要有一個初始值, 並且他不能是 undefined, 能夠是 null

compose

這裏須要先講 compose 的使用 才能順帶過渡到下面

使用

就如他的名字, 是用來組合函數的, 接受刀哥函數, 返回執行的最終函數:

// 這裏經常使用來 連接多箇中間件
const store = createStore(
    reducer,
    compose(applyMiddleware(thunk), DevTools.instrument())
)

源碼解析

他的源碼也很簡單:

function compose(...funcs) {
    if (funcs.length === 0) {
        return arg => arg
    }

    if (funcs.length === 1) {
        return funcs[0]
    }
    // 上面都是 控制, 參數數量爲 0 和 1 的狀況


    // 這裏是重點, 將循環接收到的函數數組
    return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

咱們將 reduce 的運行再度裝飾下:

// reduce 中沒有初始值的時候, 第一個 `prevValue` 是取  `funcs[0]` 的值

funcs.reduce((prevValue, currentFunc) => (...args) => prevValue(currentFunc(...args)))

reducer 返回的就是 這樣一個函數 (...args) => prevValue(currentFunc(...args)), 一層一層嵌套成一個函數

舉一個簡單的輸入例子:

var foo = compose(val => val + 10, () => 1)

foo 打印:

(...args) => a(b(...args))

執行 foo() , 返回 11

applyMiddleware

使用

applyMiddleware 是使用在 createStore 中的 enhancer 參數來加強 redux 的做用

可兼容多種三方插件, 例如 redux-thunk, redux-promise, redux-saga 等等

這裏使用官網的一個例子做爲展現:

function logger({getState}) {
    // next 就是真正的 store.dispatch
    return next => action => {
        console.log('will dispatch', action)

        const returnValue = next(action)

        console.log('state after dispatch', getState())

        return returnValue
    }
}

const store = createStore(rootReducer, {
    counter: {value: 12345}
}, applyMiddleware(logger))

源碼解析

default

function applyMiddleware(...middlewares) {
    return createStore => (...args) => {
        // 由於使用了 enhancer 參數, 他的內部沒有 createStore 的東西, 因此這裏須要從新 createStore
        const store = createStore(...args)
        let dispatch = () => {
            // 在中間件中 不容許使用 dispatch
            throw new Error(
                // 省略報錯...
            )
        }

        // 這是要傳遞的參數
        const middlewareAPI = {
            getState: store.getState,
            dispatch: (...args) => dispatch(...args)
        }

        // 從新 map 全部 middlewares 返回須要的結果
        const chain = middlewares.map(middleware => middleware(middlewareAPI))

        // 這裏就是咱們上面的 compose 相關的代碼, 返回的結果 再次執行 獲得真正的 dispatch
        dispatch = compose(...chain)(store.dispatch)

        // 返回 store 和 dispatch
        return {
            ...store,
            dispatch
        }
    }
}

這裏咱們須要從新捋一捋函數的執行, 中間件以上述的 logger 爲例子

applyMiddleware(logger) -> 返回的是一個函數(createStore) => (...args) => {/*省略*/} 我把他記爲中間件函數 1
也就是說 applyMiddleware(logger) === (createStore) => (...args) => {/*省略*/}

這個函數將在 createStore 中使用 enhancer(createStore)(reducer, preloadedState) 這裏的 enhancer 就是中間件函數 1 經過 createStore
的執行咱們能夠發現
store === createStore(reducer, preloadedState, enhancer) === enhancer(createStore)(reducer, preloadedState)
=== applyMiddleware(logger)(createStore)(reducer, preloadedState)
=== ((createStore) => (...args) => {/*省略*/})(createStore)(reducer, preloadedState)
=== 中間件函數 1 中的{/*省略*/} 返回結果 經過這一層的推論咱們能夠得出 store === 中間件函數 1中的 {/*省略*/} 返回結果

bindActionCreators

使用

這個 API 主要是用來方便 dispatch 的 他接受 2 個參數 , 第一個是對象或函數, 第二個就是 dispatch 返回值的類型很第一個參數相同

首先咱們要定義建立 action 的函數

function increment(value) {
    return {
        type: 'counter/incremented',
        payload: value
    }
}

function decrement(value) {
    return {
        type: 'counter/decremented',
        payload: value
    }
}

使用狀況 1:

function App(props) {
    const {dispatch} = props

    // 由於在 hooks 中使用 加上了 useMemo
    const fn = useMemo(() => bindActionCreators(increment, dispatch), [dispatch])

    return (
        <div className="App">
            <div>
                val: {props.value}
            </div>
            <button onClick={() => {
                fn(100)
            }}>plus
            </button>
        </div>
    );
}

使用狀況 2:

function App(props) {
    const {dispatch} = props

    const fn = useMemo(() => bindActionCreators({
        increment,
        decrement
    }, dispatch), [dispatch])


    // 若是想用 decrement 也是這樣調用 fn.decrement(100)
    return (
        <div className="App">
            <div>
                val: {props.value}
            </div>
            <button onClick={() => {
                fn.increment(100)
            }}>plus
            </button>
        </div>
    );
}

源碼解析

function bindActionCreator(actionCreator, dispatch) {
    return function () {
        // 執行 dispatch(actionCreator()) === dispatch({type:''}) 
        return dispatch(actionCreator.apply(this, arguments))
    }
}

function bindActionCreators(actionCreators, dispatch) {
    if (typeof actionCreators === 'function') {
        // 若是是函數直接執行 bindActionCreator
        return bindActionCreator(actionCreators, dispatch)
    }

    if (typeof actionCreators !== 'object' || actionCreators === null) {
        throw new Error(/*省略*/)
    }

    // 定義變量
    const boundActionCreators = {}
    
    // 由於是對象 循環遍歷, 可是 for in 效率太差
    for (const key in actionCreators) {
        const actionCreator = actionCreators[key]
       
       // 過濾
        if (typeof actionCreator === 'function') {
            // 和函數一樣 執行 bindActionCreator 而且賦值到 boundActionCreators 中
            boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
        }
    }
    return boundActionCreators
}

bindActionCreators 的源碼相對簡單一點, 理解起來相對也容易不少

總結

redux 中設計的不少地方都是很巧妙的,而且短小精悍, 值得你們做爲首次源碼閱讀的選擇

若是我講的有什麼問題, 還望不吝指教

相關文章: react-redux 源碼淺析

本文代碼倉庫: https://github.com/Grewer/rea...

參考文檔:

相關文章
相關標籤/搜索