redux 原始碼淺析
redux 版本號: "redux": "4.0.5"
redux 作為一個十分常用的狀態容器庫, 大家都應該見識過, 他很小巧, 只有 2kb, 但是珍貴的是他的 reducer
和 dispatch
這種思想方式
在閱讀此文之前, 先了解/使用 redux 相關知識點, 才能更好地閱讀本文
入口檔案
入口是在 redux/src/index.js
中, 在入口檔案中只做了一件事件, 就是引入檔案, 集中匯出
現在我們根據他匯出的方法, 來進行分析
createStore
這個是 redux 最主要的 API
使用
搭配這使用方法一起, 可以更好的瀏覽原始碼
createStore(reducer, [preloadedState], [enhancer])
他的主要功能就是建立一個 store, 將 reducer
轉換到 store
引數
一共可接受三個引數:
-
reducer (函式): 一個返回下一個狀態樹的還原函式,給定當前狀態樹和一個要處理的動作。
-
[preloadedState] (任意值): 初始值, 可以是來自於 storage 中的; 如果你用combinedReducers產生了reducer,這必須是一個普通物件,其型別與傳遞給它的鍵相同。
也可以自由地傳遞任何你的reducer能夠理解的東西。 -
[enhancer] (函式): store 的增強器, 可以選擇性的增強, 用程式碼來說就是
enhancer(createStore)(reducer, preloadedState)
,enhancer
接受的引數就是createStore
, 同樣地他也需要return
一個類似於createStore
的結果, 也就是說, 只有我們返回的是 一個像createStore
的東西,
他的具體實現我們就可以有很多微調 這裡附上一篇探討enhancer
和applyMiddleware
的文章 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
參考文件: