本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
【若川视野 x 源码共读】学习 redux 源码整体架构,深入理解 redux 及其中间件原理 点击了解本期详情一起参与。
今天阅读的是:Redux
当前版本为
4.2.0
- Redux是用来做状态管理的,与我们熟知的其他状态管理工具库,如
vuex,pinia,Mobx
等类似
尝试使用
- 打开
package.json
,我们可以看到build
命令是执行rollup
编译打包
- 打开
rollup.config.js
开启sourceMap
,然后运行编译
npm i
npm build
打包后可以在dist
中看到打包产物
以计数器为例,创建html
文件
<!DOCTYPE html>
<html>
<head>
<title>Redux basic example</title>
<script src="../dist/redux.js"></script>
</head>
<body>
<div>
<p>
Clicked: <span id="value">0</span> times
<button id="increment">+</button>
<button id="decrement">-</button>
<button id="incrementIfOdd">Increment if odd</button>
<button id="incrementAsync">Increment async</button>
</p>
</div>
<script>
// 定义store
const actionTypes = {
INCREMENT: 'INCREMENT',
DECREMENT: 'DECREMENT'
}
const initialState = {
counter: 0
}
function reducer(state = initialState, action) {
switch (action.type) {
case actionTypes.INCREMENT:
return { ...state, counter: state.counter + 1 }
case actionTypes.DECREMENT:
return { ...state, counter: state.counter - 1 }
default:
return state
}
}
const store = Redux.createStore(reducer)
const countElement = document.getElementById('value')
function render() {
const { counter } = store.getState()
countElement.innerHTML = counter.toString()
}
render()
store.subscribe(render)
// 监听按钮事件
document
.getElementById('increment')
.addEventListener('click', function () {
store.dispatch({ type: actionTypes.INCREMENT })
})
document
.getElementById('decrement')
.addEventListener('click', function () {
store.dispatch({ type: actionTypes.DECREMENT })
})
document
.getElementById('incrementIfOdd')
.addEventListener('click', function () {
if (store.getState() % 2 !== 0) {
store.dispatch({ type: actionTypes.INCREMENT })
}
})
document
.getElementById('incrementAsync')
.addEventListener('click', function () {
setTimeout(function () {
store.dispatch({ type: actionTypes.INCREMENT })
}, 1000)
})
</script>
</body>
</html>
开始调试
- 对应地方打上断点,按
F5
进入调试
- 按下
F11
进去createStore
函数中
export default function createStore<
S,
A extends Action,
Ext = {},
StateExt = never
>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
enhancer?: StoreEnhancer<Ext, StateExt>
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext {
// 省略...
// 合法性校验
// 当前的reducer
let currentReducer = reducer
// 当前的state
let currentState = preloadedState as S
// 当前store中的监听函数
let currentListeners: (() => void)[] | null = []
// 下一次dispatch的监听函数
let nextListeners = currentListeners
// 是否在派发状态
let isDispatching = false
// 异常兜底
function ensureCanMutateNextListeners() {}
// 获取最新的state
function getState(): S {}
// 订阅
function subscribe(listener: () => void) {}
// 派发事件
function dispatch(action: A) {}
// 替换当前的reducer
function replaceReducer<NewState, NewActions extends A>(
nextReducer: Reducer<NewState, NewActions>
): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext {}
// 提供一个最小的可观察接口
function observable() {}
// 初始化
dispatch({ type: ActionTypes.INIT } as A)
const store = {
dispatch: dispatch as Dispatch<A>,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
} as unknown as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
return store
}
源码解析
我们可以看到,整个store中包含了几个部分
-
state
-
subscribe
-
subscribe
-
getState
-
dispatch
-
replaceReducer
(开发库使用) -
observable
(开发库使用)
getState
- 返回最新的state
/**
* Reads the state tree managed by the store.
*
* @returns The current state tree of your application.
*/
function getState(): S {
if (isDispatching) {
throw new Error(
'You may not call store.getState() while the reducer is executing. ' +
'The reducer has already received the state as an argument. ' +
'Pass it down from the top reducer instead of reading it from the store.'
)
}
return currentState as S
}
subscribe
function subscribe(listener: () => void) {
// 省略校验部分...
let isSubscribed = true
ensureCanMutateNextListeners()
// 添加监听函数到下一次dispatch中
nextListeners.push(listener)
// 返回取消订阅函数
return function unsubscribe() {
// 省略校验部分...
isSubscribed = false
ensureCanMutateNextListeners()
// 从下一轮dispatch队列中剔除
// 重置当前队列
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
ensureCanMutateNextListeners
- 利用
slice
做一个浅拷贝数组,切断引用
/**
* This makes a shallow copy of currentListeners so we can use
* nextListeners as a temporary list while dispatching.
*
* This prevents any bugs around consumers calling
* subscribe/unsubscribe in the middle of a dispatch.
*/
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
dispatch
function dispatch(action: A) {
// 省略校验部分...
try {
isDispatching = true
// 调用reducer,获取最新的state
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
const listeners = (currentListeners = nextListeners)
// 通知所有的listeners
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
综上,createStore
提供一个全局的状态state
,并提供了dispatch()
方法统一的操作state
,并通知所有的订阅者(执行所有的listeners)达到更新页面并渲染的目的。
中间件 applyMiddleware
现在有个需求,我需要打印每次派发的action,做一个日志打印的功能
我们需要对每次的action做一个拦截并打印,随后释放出去,执行action
这时候需要引入中间件
- 页面中添加
log
方法
function log(store) {
// 存储起来store
const next = store.dispatch
function logAndDispatch(action) {
console.log('当前派发的action', action)
// dispatch
next(action)
console.log('派发后的结果', store.getState())
}
// monkey patch
store.dispatch = logAndDispatch
}
log(store)
通过上述函数,即可实现功能。但我们需要考虑一个情况,如果存在多个中间件,每次都要这样调用,代码可读性会很差,我们可以试着对他们执行做一层封装
function applyMiddleware(store, ...fns) {
fns.forEach((fn) => {
fn(store)
})
}
这就是简易版的applyMiddleware
实现,接下来,我们来看看redux
提供的函数
function applyMiddleware() {
// 数组存储中间件函数
for (var _len = arguments.length, middlewares = new Array(_len), _key = 0; _key < _len; _key++) {
middlewares[_key] = arguments[_key];
}
return function (createStore) {
return function (reducer, preloadedState) {
var store = createStore(reducer, preloadedState);
var _dispatch = function dispatch() {
throw new Error('Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.');
};
var middlewareAPI = {
getState: store.getState,
dispatch: function dispatch(action) {
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
args[_key2 - 1] = arguments[_key2];
}
return _dispatch.apply(void 0, [action].concat(args));
}
};
var chain = middlewares.map(function (middleware) {
return middleware(middlewareAPI);
});
// 将store传入,执行middleware
_dispatch = compose.apply(void 0, chain)(store.dispatch);
return _objectSpread2(_objectSpread2({}, store), {}, {
dispatch: _dispatch
});
};
};
}
综上看,中间件有点像是koa
当中的洋葱圈模型,通过不断的compose.apply
不断执行middleware
借用川哥文章中的图,redux
工作流程图