redux 原始碼淺析

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

redux 作為一個十分常用的狀態容器庫, 大家都應該見識過, 他很小巧, 只有 2kb, 但是珍貴的是他的 reducerdispatch 這種思想方式

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

入口檔案

入口是在 redux/src/index.js 中, 在入口檔案中只做了一件事件, 就是引入檔案, 集中匯出

現在我們根據他匯出的方法, 來進行分析

createStore

這個是 redux 最主要的 API

使用

搭配這使用方法一起, 可以更好的瀏覽原始碼

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

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

引數

一共可接受三個引數:

  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/6844903543502012429

// 簡單的例子:

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, 他有自己的 api

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/6844903714998730766

剩餘程式碼

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/react-redux-notes

參考文件: