Redux源码阅读笔记
本文是我阅读 redux 源码的一些笔记。同时提了一些问题。有一些问题我自己有答案,有一些没有,一并记之。这篇笔记的最主要目的是拆解 middleware 和 redux-thunk 的源码;但为了使拆解过程好懂,我也特意构造了一些从简单到复杂的例子,逐步深入。
基本用法和源码
我们先来看 redux 的基本用法。为了突出“它和 GUI 没有关系”这件事,我用 nodejs 来跑,而不是用 HTML 写个按钮来演示。特别的,它和 react 没有关系。
// 演示代码
const redux = require("redux")
const reducer = (state, action) => {
if (state == null) return 0;
if (action.type === 'inc') return state + 1;
if (action.type === 'dec') return state - 1;
return state;
};
const store = redux.createStore(reducer);
store.subscribe(() => { console.log(store.getState()); });
store.dispatch({ type: 'inc' });
store.dispatch({ type: 'inc' });
store.dispatch({ type: 'dec' });
store.dispatch({ type: 'dec' });
// 运行结果
$ node a.js
1
2
1
0
可以看到,这里面出现了几个东西:store.getState(), store.subscribe(), store.dispatch()。要注意我们把state的结构放在了 reducer 的第一行里。
我们来看下(精简后的)相关源码:
// redux 源码(精简版)
export default function createStore(reducer, preloadedState, enhancer) {
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
function getState() {
return currentState
}
function subscribe(listener) {
let isSubscribed = true
currentListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) return;
isSubscribed = false
const index = currentListeners.indexOf(listener)
currentListeners.splice(index, 1)
}
}
function dispatch(action) {
currentState = currentReducer(currentState, action)
for (let i = 0; i < currentListeners.length; i++) {
currentListeners[i]()
}
return action
}
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable,
}
}
这里有个问题
为什么listener没有把 state 作为参数?也没用 action 作为参数? 后面有讨论。
对于 action 的要求
- 需要是 plainObject (stackoverflow.com/questions/5…)
- 需要有 type 属性
// redux 源码(精简版)
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(
`Actions must be plain objects. Instead, the actual type was: '${kindOf(
action
)}'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions. See https://redux.js.org/tutorials/fundamentals/part-4-store#middleware and https://redux.js.org/tutorials/fundamentals/part-6-async-logic#using-the-redux-thunk-middleware for examples.`
)
}
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. You may have misspelled an action type string constant.'
)
}
...
}
// redux 源码(精简版)
/**
* @param {any} obj The object to inspect.
* @returns {boolean} True if the argument appears to be a plain object.
*/
export default function isPlainObject(obj) {
if (typeof obj !== 'object' || obj === null) return false
let proto = obj
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto)
}
return Object.getPrototypeOf(obj) === proto
}
问题:dispatch 为什么返回action?
// redux 源码(精简版)
function dispatch(action) {
currentState = currentReducer(currentState, action)
for (let i = 0; i < currentListeners.length; i++) {
currentListeners[i]()
}
return action
}
可以看到 dispatch 返回了 action。这个设计的考虑是什么?
多层级 state 和 多层级 reducer
// 演示代码
const redux = require("redux")
const reducer = (state, action) => {
if (state == null) return {
'a': { 'b': { 'c': 0 } },
'd': { 'e': { 'f': 0 } },
};
if (action.type === 'a.b.c.inc') return {
'a': { 'b': { 'c': state.a.b.c + 1 } },
'd': { 'e': { 'f': state.d.e.f } },
};
if (action.type === 'a.b.c.dec') return {
'a': { 'b': { 'c': state.a.b.c - 1 } },
'd': { 'e': { 'f': state.d.e.f } },
};
if (action.type === 'd.e.f.inc') return {
'a': { 'b': { 'c': state.a.b.c } },
'd': { 'e': { 'f': state.d.e.f + 1 } },
};
if (action.type === 'd.e.f.dec') return {
'a': { 'b': { 'c': state.a.b.c } },
'd': { 'e': { 'f': state.d.e.f - 1 } },
};
return state;
};
const store = redux.createStore(reducer);
store.subscribe(() => {
console.log('a.b.c=', store.getState().a.b.c,
'd.e.f=', store.getState().d.e.f);
});
store.dispatch({ type: 'a.b.c.inc' });
store.dispatch({ type: 'a.b.c.inc' });
store.dispatch({ type: 'a.b.c.dec' });
store.dispatch({ type: 'a.b.c.dec' });
store.dispatch({ type: 'd.e.f.inc' });
store.dispatch({ type: 'd.e.f.inc' });
store.dispatch({ type: 'd.e.f.dec' });
store.dispatch({ type: 'd.e.f.dec' });
// 运行结果
$ node a.js 130 ↵
a.b.c= 1 d.e.f= 0
a.b.c= 2 d.e.f= 0
a.b.c= 1 d.e.f= 0
a.b.c= 0 d.e.f= 0
a.b.c= 0 d.e.f= 1
a.b.c= 0 d.e.f= 2
a.b.c= 0 d.e.f= 1
a.b.c= 0 d.e.f= 0
这里有个问题:
为什么reducer里是返回新的 object?而不是直接修改原来 object 的 property?后面有答案。
回到主线:
我们可以把state和对应的reducer拆分在不同的地方 ---- 当项目越来越大时,这个就很有必要了 ---- 想象一下如果你的 state 里有 1000 个 property ?这时候可以这么写:
// 演示代码
const redux = require("redux")
const reducer1 = (state, action) => {
if (state == null) return {'b': {'c': 0} };
if (action.type === 'a.b.c.inc') return {'b': {'c': state.b.c + 1} };
if (action.type === 'a.b.c.dec') return {'b': {'c': state.b.c - 1} };
return state;
};
const reducer2 = (state, action) => {
if (state == null) return {'e': {'f': 0} };
if (action.type === 'd.e.f.inc') return {'e': {'f': state.e.f + 1} };
if (action.type === 'd.e.f.dec') return {'e': {'f': state.e.f - 1} };
return state;
};
const reducer = redux.combineReducers({ 'a': reducer1, 'd': reducer2 });
const store = redux.createStore(reducer);
store.subscribe(() => {
console.log('a.b.c=', store.getState().a.b.c,
'd.e.f=', store.getState().d.e.f);
});
store.dispatch({ type: 'a.b.c.inc' });
store.dispatch({ type: 'a.b.c.inc' });
store.dispatch({ type: 'a.b.c.dec' });
store.dispatch({ type: 'a.b.c.dec' });
store.dispatch({ type: 'd.e.f.inc' });
store.dispatch({ type: 'd.e.f.inc' });
store.dispatch({ type: 'd.e.f.dec' });
store.dispatch({ type: 'd.e.f.dec' });
// 运行结果
$ node a.js 130 ↵
a.b.c= 1 d.e.f= 0
a.b.c= 2 d.e.f= 0
a.b.c= 1 d.e.f= 0
a.b.c= 0 d.e.f= 0
a.b.c= 0 d.e.f= 1
a.b.c= 0 d.e.f= 2
a.b.c= 0 d.e.f= 1
a.b.c= 0 d.e.f= 0
我们来看精简后的相关源码:
// redux 源码(精简版)
export default function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
return function combination(state = {}, action) {
let hasChanged = false
const nextState = {}
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)
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
hasChanged =
hasChanged || finalReducerKeys.length !== Object.keys(state).length
return hasChanged ? nextState : state
}
}
其中 const nextStateForKey = reducer(previousStateForKey, action) 和 nextState[key] = nextStateForKey 是关键。
问:为何 reducer 返回全新的 object 呢?
是否可以像下面这么写呢?
// 演示代码
const redux = require("redux")
const reducer = (state, action) => {
if (state == null) return {
'a': { 'b': { 'c': 0 } },
'd': { 'e': { 'f': 0 } },
};
if (action.type === 'a.b.c.inc') state.a.b.c += 1;
if (action.type === 'a.b.c.dec') state.a.b.c -= 1;
if (action.type === 'd.e.f.inc') state.d.e.f += 1;
if (action.type === 'd.e.f.dec') state.d.e.f -= 1;
return state;
};
const store = redux.createStore(reducer);
store.subscribe(() => {
console.log('a.b.c=', store.getState().a.b.c,
'd.e.f=', store.getState().d.e.f);
});
store.dispatch({ type: 'a.b.c.inc' });
store.dispatch({ type: 'a.b.c.inc' });
store.dispatch({ type: 'a.b.c.dec' });
store.dispatch({ type: 'a.b.c.dec' });
store.dispatch({ type: 'd.e.f.inc' });
store.dispatch({ type: 'd.e.f.inc' });
store.dispatch({ type: 'd.e.f.dec' });
store.dispatch({ type: 'd.e.f.dec' });
// 运行结果
$ node a.js
a.b.c= 1 d.e.f= 0
a.b.c= 2 d.e.f= 0
a.b.c= 1 d.e.f= 0
a.b.c= 0 d.e.f= 0
a.b.c= 0 d.e.f= 1
a.b.c= 0 d.e.f= 2
a.b.c= 0 d.e.f= 1
a.b.c= 0 d.e.f= 0
其实也不是不可以。只不过会丧失“快速定位state是否有变化”的能力。想象这个两种场景:
- 有一个action,是没有任何 reducer 认识的,那么 state 中所有的状态都不会改变。但这时候reducer返回后,你怎么判断state是否有变动呢?这种需求是存在的,比如当state都没有变的时候,就不去更新UI了,从而可以优化性能。
- state里有很多内容,按照其功效可拆分为几个不同的部分;这时候有一个action改变了其中一部分;你想知道是哪部分改变了,不变的部分就不做后续操作了。这种需求也是存在的。比如你只改了一个影响显示模式的state,但你可能并不想触发 IO 操作。
redux的解决办法是,通过约定:如果 reducer 返回的state的地址不变,则代表 state 中的内容都不变;如果 state 的地址变了,但它的某个成员变量的地址没有变,那么代表那个成员变量的内容都不变(比如,dispatch 后 state 的地址变了,state.a 的地址也变了,但是 state.b 和 state.c 的地址都没有变,那就代表你只需要去关系 state.a 的变动即可)。
从这个意义出发,当state中有很多个层级时,combineReducers 是一个很好的工具。
思考题一
下面的写法中,state里的 a.b.c 是怎么拼出来的?怎样快速判断 a.b.c 变了,d.e.f 没变?
// 演示代码
const redux = require("redux")
const reducer1 = (state, action) => {
if (state == null) return 0;
if (action.type === 'a.b.c.inc') return state + 1;
if (action.type === 'a.b.c.dec') return state - 1;
return state;
};
const reducer2 = (state, action) => {
if (state == null) return 0;
if (action.type === 'd.e.f.inc') return state + 1;
if (action.type === 'd.e.f.dec') return state - 1;
return state;
};
const cr = redux.combineReducers;
const reducer = cr({
'a': cr({'b': cr({'c': reducer1 })}),
'd': cr({'e': cr({'f': reducer2 })})
});
const store = redux.createStore(reducer);
store.subscribe(() => {
console.log('a.b.c=', store.getState().a.b.c,
'd.e.f=', store.getState().d.e.f);
});
store.dispatch({ type: 'a.b.c.inc' });
store.dispatch({ type: 'a.b.c.inc' });
store.dispatch({ type: 'a.b.c.dec' });
store.dispatch({ type: 'a.b.c.dec' });
store.dispatch({ type: 'd.e.f.inc' });
store.dispatch({ type: 'd.e.f.inc' });
store.dispatch({ type: 'd.e.f.dec' });
store.dispatch({ type: 'd.e.f.dec' });
// 运行结果
$ node a.js
a.b.c= 1 d.e.f= 0
a.b.c= 2 d.e.f= 0
a.b.c= 1 d.e.f= 0
a.b.c= 0 d.e.f= 0
a.b.c= 0 d.e.f= 1
a.b.c= 0 d.e.f= 2
a.b.c= 0 d.e.f= 1
a.b.c= 0 d.e.f= 0
思考题二
把上面的写法中的 cr 去掉一些,变成下面的写法,为什么会有错误:
// 演示代码
const redux = require("redux")
const reducer1 = (state, action) => {
if (state == null) return 0;
if (action.type === 'a.b.c.inc') return state + 1;
if (action.type === 'a.b.c.dec') return state - 1;
return state;
};
const reducer2 = (state, action) => {
if (state == null) return 0;
if (action.type === 'd.e.f.inc') return state + 1;
if (action.type === 'd.e.f.dec') return state - 1;
return state;
};
const cr = redux.combineReducers;
const reducer = cr({
'a': {'b': {'c': reducer1 }},
'd': {'e': {'f': reducer2 }}
});
const store = redux.createStore(reducer);
store.subscribe(() => {
console.log('a.b.c=', store.getState().a.b.c,
'd.e.f=', store.getState().d.e.f);
});
store.dispatch({ type: 'a.b.c.inc' });
store.dispatch({ type: 'a.b.c.inc' });
store.dispatch({ type: 'a.b.c.dec' });
store.dispatch({ type: 'a.b.c.dec' });
store.dispatch({ type: 'd.e.f.inc' });
store.dispatch({ type: 'd.e.f.inc' });
store.dispatch({ type: 'd.e.f.dec' });
store.dispatch({ type: 'd.e.f.dec' });
// 运行结果
$ node a.js
Store does not have a valid reducer. Make sure the argument passed to combineReducers is an object whose values are reducers.
Store does not have a valid reducer. Make sure the argument passed to combineReducers is an object whose values are reducers.
/private/tmp/a.js:25
console.log('a.b.c=', store.getState().a.b.c,
^
TypeError: Cannot read properties of undefined (reading 'b')
at /private/tmp/a.js:25:45
at Object.dispatch (/usr/local/lib/node_modules/redux/lib/redux.js:305:7)
at Object.<anonymous> (/private/tmp/a.js:29:7)
at Module._compile (node:internal/modules/cjs/loader:1101:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
at Module.load (node:internal/modules/cjs/loader:981:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
at node:internal/main/run_main_module:17:47
问:为什么 listener 没有 action 参数?
我的理解是,因为有了上面的快速判断 state 任意一部分是否变化的能力(通过比较变化前后的指针是否相同),所以可以不提供 action。redux 期待你的 listerner 从“state发生了改变,我该为此做什么”来考虑问题,而不是“是什么导致了state的变化,以及我该为此做些什么”。大部分情况下,这有助于解耦。
但还有一种需求,监听event并触发一些不改变state的事 (比如发个心跳网络包出去)。常规的redux看起来并不适合这种需求。但是后面介绍 redux-thunk 时会介绍怎么近似的解决这种需求。
问:listener 中调用 subscribe 可以吗?
可以。
// redux 源码(精简版)
let currentListeners = []
let nextListeners = currentListeners
function subscribe(listener) {
...
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (isDispatching) {
throw new Error(
'You may not unsubscribe from a store listener while the reducer is executing. ' +
'See https://redux.js.org/api/store#subscribelistener for more details.'
)
}
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
function dispatch(action) {
...
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
...
}
/**
* 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()
}
}
中间件
中间件的用法
中间件的基本用法
// 演示代码
const redux = require("redux")
const reducer = (state, action) => {
if (state == null) return 0;
if (action.type === 'inc') return state + 1;
if (action.type === 'dec') return state - 1;
return state;
}
function middleware() {
return function dispatch_2_dispatch(old_dispatch) {
return function newdispatch(action) {
console.log("entering middleware");
const tmp = old_dispatch(action);
console.log("leaving middleware");
return tmp;
}
};
}
const store = redux.createStore(reducer, redux.applyMiddleware(middleware));
store.subscribe(() => { console.log(store.getState()); });
store.dispatch({ type: 'inc' });
store.dispatch({ type: 'inc' });
store.dispatch({ type: 'dec' });
store.dispatch({ type: 'dec' });
// 运行结果
$ node a.js
entering middleware
1
leaving middleware
entering middleware
2
leaving middleware
entering middleware
1
leaving middleware
entering middleware
0
leaving middleware
多个中间件的场景
// 演示代码
const redux = require("redux")
const reducer = (state, action) => {
if (state == null) return 0;
if (action.type === 'inc') return state + 1;
if (action.type === 'dec') return state - 1;
return state;
}
function middleware1() {
return function dispatch_2_dispatch(old_dispatch) {
return function newdispatch(action) {
console.log("entering middleware1");
const tmp = old_dispatch(action);
console.log("leaving middleware1");
return tmp;
}
};
}
function middleware2() {
return function dispatch_2_dispatch(old_dispatch) {
return function newdispatch(action) {
console.log("entering middleware2");
const tmp = old_dispatch(action);
console.log("leaving middleware2");
return tmp;
}
};
}
const store = redux.createStore(reducer,
redux.applyMiddleware(middleware1, middleware2));
store.subscribe(() => { console.log(store.getState()); });
store.dispatch({ type: 'inc' });
store.dispatch({ type: 'inc' });
store.dispatch({ type: 'dec' });
store.dispatch({ type: 'dec' });
// 运行结果
$ node a.js
entering middleware1
entering middleware2
1
leaving middleware2
leaving middleware1
entering middleware1
entering middleware2
2
leaving middleware2
leaving middleware1
entering middleware1
entering middleware2
1
leaving middleware2
leaving middleware1
entering middleware1
entering middleware2
0
leaving middleware2
leaving middleware1
斩断中间件链条的场景
// 演示代码
const redux = require("redux")
const reducer = (state, action) => {
if (state == null) return 0;
if (action.type === 'inc') return state + 1;
if (action.type === 'dec') return state - 1;
return state;
}
function middleware1() {
return function dispatch_2_dispatch() {
return function newdispatch() {
console.log("entering middleware1");
console.log("cut the chain");
console.log("leaving middleware1");
}
};
}
function middleware2() {
return function dispatch_2_dispatch(old_dispatch) {
return function newdispatch(action) {
console.log("entering middleware2");
const tmp = old_dispatch(action);
console.log("leaving middleware2");
return tmp;
}
};
}
const store = redux.createStore(reducer,
redux.applyMiddleware(middleware1, middleware2));
store.subscribe(() => { console.log(store.getState()); });
store.dispatch({ type: 'inc' });
store.dispatch({ type: 'inc' });
store.dispatch({ type: 'dec' });
store.dispatch({ type: 'dec' });
// 运行结果
$ node a.js
entering middleware1
cut the chain
leaving middleware1
entering middleware1
cut the chain
leaving middleware1
entering middleware1
cut the chain
leaving middleware1
entering middleware1
cut the chain
leaving middleware1
总结中间件的格式
首先我们知道 dispatch 的格式总是:
function newdispatch(action) {
// blabla...
}
而从上面我们总结 middleware 的格式是:
function middleware() {
return function dispatch_2_dispatch(old_dispatch) {
new_dispatch = ...;
return new_dispatch;
};
}
思考题:为什么 middleware 要设计成有 ()?
middleware的格式是redux设计的。为什么它不设计成这样?
// 实现 middleware 的时候
function middleware1(old_dispatch) {
new_dispatch = ...;
return new_dispatch;
}
function middleware2(old_dispatch) {
new_dispatch = ...;
return new_dispatch;
}
// 使用 middleware 的时候
const store = redux.createStore(reducer, redux.applyMiddleware(middleware1, middleware2));
当然这需要修改 redux.applyMiddleware 的源码,使得支持这种函数格式。但这是做得到的,如果一开始就这么设计的话。
我的理解是,主要还是因为,想要在 middleware 里做更多的事,比如下面这个。
在中间件中触发新的 dispatch
// 演示代码
const redux = require("redux")
const reducer = (state, action) => {
if (state == null) return 0;
if (action.type === 'inc') return state + 1;
if (action.type === 'dec') return state - 1;
return state;
}
function middleware({dispatch, getState}) {
return function dispatch_2_dispatch(old_dispatch) {
return function newdispatch(action) {
old_dispatch(action);
if (getState() == 2 && action.type === 'inc')
dispatch({ type: 'inc' });
if (getState() == 2 && action.type === 'dec')
dispatch({ type: 'dec' });
}
};
}
const store = redux.createStore(reducer, redux.applyMiddleware(middleware));
store.subscribe(() => { console.log(store.getState()); });
store.dispatch({ type: 'inc' });
store.dispatch({ type: 'inc' });
store.dispatch({ type: 'dec' });
store.dispatch({ type: 'dec' });
// 运行结果
$ node a.js
1
2
3
2
1
0
注意 middleware 函数加了参数 {dispatch, getState}。这里的 dispatch 在实际运行中,是整个 store 设置好了后的最终 dispatch 函数。也就是说,可以在 middleware 中检查当时的 state 的状态,也可以直接发起一轮全新的 action。从而增加了灵活度。
中间件的源码
我们挑选4段代码发在一起,互相参照。
// 中间件的一个例子,作为读源码的参考
function middleware() {
return function dispatch_2_dispatch(old_dispatch) {
return function newdispatch(action) {
console.log("entering middleware");
const tmp = old_dispatch(action);
console.log("leaving middleware");
return tmp;
}
};
}
const store = redux.createStore(reducer, redux.applyMiddleware(middleware));
// redux 源码(精简版)
export default function createStore(reducer, preloadedState, enhancer) {
// ...
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error()
}
return enhancer(createStore)(reducer, preloadedState)
}
// redux 源码(精简版)
export default function applyMiddleware(...middlewares) {
return (createStore) => (...args) => {
const store = createStore(...args)
let dispatch = () => { throw new Error() }
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args),
}
const chain = middlewares.map((middleware) => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch,
}
}
}
// redux 源码(精简版)
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)))
}
applyMiddleware 那段有点费解,所以下面会逐渐展开剖析一下。
假如没有 middlewareAPI 参数
applyMiddleware 里面费解的是 chain 的那两句。我们拆解一下。首先假设没有 middlewareAPI 那个东西(这是可以理解的,其实它就对应了我们之前那个 middleware 函数没有带参数的3个例子),那就会变成
// 中间件的2个例子,作为读源码的参考
function middleware1() {
return function dispatch_2_dispatch(old_dispatch) {
new_dispatch = ...;
return new_dispatch;
};
}
function middleware2() {
return function dispatch_2_dispatch(old_dispatch) {
new_dispatch = ...;
return new_dispatch;
};
}
const store = redux.createStore(reducer, redux.applyMiddleware(middleware1, middleware2));
// redux 源码(精简版)
export default function applyMiddleware(...middlewares) {
return (createStore) => (...args) => {
const store = createStore(...args)
let dispatch = () => { throw new Error() }
const chain = middlewares.map((middleware) => middleware())
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch,
}
}
}
我们手工把chain那两行展开一下:
// redux 源码(推演版)
export default function applyMiddleware(...middlewares) {
return (createStore) => (...args) => {
const store = createStore(...args)
let dispatch = () => { throw new Error() }
// const chain = middlewares.map((middleware) => middleware())
chain0 = middleware0()
chain1 = middleware1()
// ...
chainN = middlewareN()
// dispatch = compose(...chain)(store.dispatch)
dispatchtmp = store.dispatch
dispatchtmp = chainN(dispatchtmp)
// ...
dispatchtmp = chain1(dispatchtmp)
dispatchtmp = chain0(dispatchtmp)
dispatch = dispatchtmp
return {
...store,
dispatch,
}
}
}
export default function applyMiddleware(...middlewares) {
return (createStore) => (...args) => {
const store = createStore(...args)
let dispatch = () => { throw new Error() }
dispatchtmp = store.dispatch
dispatchtmp = middlewareN()(dispatchtmp)
// ...
dispatchtmp = middleware1()(dispatchtmp)
dispatchtmp = middleware0()(dispatchtmp)
dispatch = dispatchtmp
return {
...store,
dispatch,
}
}
}
export default function applyMiddleware(...middlewares) {
return (createStore) => (...args) => {
const store = createStore(...args)
let dispatch = () => { throw new Error() }
dispatchtmp = store.dispatch
dispatchtmp = dispatch_2_dispatch_N(dispatchtmp)
// ...
dispatchtmp = dispatch_2_dispatch_1(dispatchtmp)
dispatchtmp = dispatch_2_dispatch_0(dispatchtmp)
dispatch = dispatchtmp
return {
...store,
dispatch,
}
}
}
可以看到,const chain = middlewares.map((middleware) => middleware()) 其实就是在获得一个一个的 dispatch_2_dispatch 函数,然后 compose(...chain) 就是把它们嵌套起来变成一个大的 dispatch_2_dispatch 函数,然后 compose(...chain)(store.dispatch) 就是一个 dispatch 函数,而最后 compose(...chain)(store.dispatch)(action) 就会调一个一个的 middleware + reducer。
有 middlewareAPI 参数的情况
其实 middlewareAPI 只给 middleware 用到一下。但结果是没有变的,const chain = middlewares.map((middleware) => middleware()) 其实就是在获得一个一个的 dispatch_2_dispatch 函数。其他的和上面的流程没有什么区别。至于为什么要有这个,上面的例子已经说过了。
// 中间件的例子,作为读源码的参考
function middleware({dispatch, getState}) {
return function dispatch_2_dispatch(old_dispatch) {
return function newdispatch(action) {
old_dispatch(action);
if (getState() == 2 && action.type === 'inc')
dispatch({ type: 'inc' });
if (getState() == 2 && action.type === 'dec')
dispatch({ type: 'dec' });
}
};
}
// redux 源码(精简版)
export default function applyMiddleware(...middlewares) {
return (createStore) => (...args) => {
const store = createStore(...args)
let dispatch = () => { throw new Error() }
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args),
}
const chain = middlewares.map((middleware) => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch,
}
}
}
为了加深理解,下面依旧展开一下:
// redux 源码(推演版)
export default function applyMiddleware(...middlewares) {
return (createStore) => (...args) => {
const store = createStore(...args)
let dispatch = () => { throw new Error() }
// const chain = middlewares.map((middleware) => middleware(middlewareAPI))
const foo = (...args) => dispatch(...args)
chain0 = middleware0({getState: store.getState, dispatch: foo})
chain1 = middleware1({getState: store.getState, dispatch: foo})
// ...
chainN = middlewareN({getState: store.getState, dispatch: foo})
// dispatch = compose(...chain)(store.dispatch)
dispatchtmp = store.dispatch
dispatchtmp = chainN(dispatchtmp)
// ...
dispatchtmp = chain1(dispatchtmp)
dispatchtmp = chain0(dispatchtmp)
dispatch = dispatchtmp
return {
...store,
dispatch,
}
}
}
// redux 源码(推演版)
export default function applyMiddleware(...middlewares) {
return (createStore) => (...args) => {
const store = createStore(...args)
let dispatch = () => { throw new Error() }
const foo = (...args) => dispatch(...args)
dispatchtmp = store.dispatch
dispatchtmp = middlewareN({getState: store.getState, dispatch: foo})(dispatchtmp)
// ...
dispatchtmp = middleware1({getState: store.getState, dispatch: foo})(dispatchtmp)
dispatchtmp = middleware0({getState: store.getState, dispatch: foo})(dispatchtmp)
dispatch = dispatchtmp
return {
...store,
dispatch,
}
}
}
// redux 源码(推演版)
export default function applyMiddleware(...middlewares) {
return (createStore) => (...args) => {
const store = createStore(...args)
let dispatch = () => { throw new Error() }
dispatchtmp = store.dispatch
dispatchtmp = dispatch_2_dispatch_N(dispatchtmp)
// ...
dispatchtmp = dispatch_2_dispatch_1(dispatchtmp)
dispatchtmp = dispatch_2_dispatch_0(dispatchtmp)
dispatch = dispatchtmp
return {
...store,
dispatch,
}
}
}
问:dispatch: (...args) => dispatch(...args) 这句为什么这么啰嗦?
下面这两种写法有什么区别?我也不是很懂。
let dispatch = () => { throw new Error() }
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args),
}
const chain = middlewares.map((middleware) => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
let dispatch = () => { throw new Error() }
const middlewareAPI = {
getState: store.getState,
dispatch: dispatch,
}
const chain = middlewares.map((middleware) => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
作为参考,把刚刚那两段代码各自展开一下:
let dispatch = () => { throw new Error() }
const foo = (...args) => dispatch(...args)
dispatchtmp = store.dispatch
dispatchtmp = middlewareN({getState: store.getState, dispatch: foo})(dispatchtmp)
// ...
dispatchtmp = middleware1({getState: store.getState, dispatch: foo})(dispatchtmp)
dispatchtmp = middleware0({getState: store.getState, dispatch: foo})(dispatchtmp)
dispatch = dispatchtmp
let dispatch = () => { throw new Error() }
dispatchtmp = store.dispatch
dispatchtmp = middlewareN({getState: store.getState, dispatch: dispatch})(dispatchtmp)
// ...
dispatchtmp = middleware1({getState: store.getState, dispatch: dispatch})(dispatchtmp)
dispatchtmp = middleware0({getState: store.getState, dispatch: dispatch})(dispatchtmp)
dispatch = dispatchtmp
这个问题我也不知道。
问:middleware({dispatch, getState}) 可以设计成 middleware(store) 吗?
我们已经知道,middleware带参数{dispatch, getState},是为了可以在新的dispatch中,可以自行判断state,以及触发新一轮的 dispatch。可以看下这个例子:
function middleware({dispatch, getState}) {
return function dispatch_2_dispatch(old_dispatch) {
return function newdispatch(action) {
old_dispatch(action);
if (getState() == 2 && action.type === 'inc')
dispatch({ type: 'inc' });
if (getState() == 2 && action.type === 'dec')
dispatch({ type: 'dec' });
}
};
}
那么,可以把参数{dispatch, getState}改成store吗?也一样能达到目的:
function middleware(store) {
return function dispatch_2_dispatch(old_dispatch) {
return function newdispatch(action) {
old_dispatch(action);
if (store.getState() == 2 && action.type === 'inc')
store.dispatch({ type: 'inc' });
if (store.getState() == 2 && action.type === 'dec')
store.dispatch({ type: 'dec' });
}
};
}
当然 applyMiddleware 也需要变一变:
export default function applyMiddleware(...middlewares) {
return (createStore) => (...args) => {
const store = createStore(...args)
let dispatch = () => { throw new Error() }
const chain = middlewares.map((middleware) => middleware(store))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch,
}
}
}
我感觉似乎可以。除了它这个参数暴露的信息有点多而已。
问:let dispatch = () => { throw new Error() } 是怎么起作用的?
在源码中,dispatch先定义为报错。在调用 middleware() 的时候,如果调用 dispatch ,就会报错。但是,等dispatch 被替换成 compose(...chain)(store.dispatch) 后,再调用dispatch,就没事啦。
// redux 源码
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args),
}
const chain = middlewares.map((middleware) => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
比例,下面这个会在throw:
// 演示代码
const redux = require("redux")
const reducer = (state, action) => {
if (state == null) return 0;
if (action.type === 'inc') return state + 1;
if (action.type === 'dec') return state - 1;
return state;
}
function middleware({dispatch, getState}) {
dispatch({type: 'xxx'}); // <---- this line
return function dispatch_2_dispatch(old_dispatch) {
return function newdispatch(action) {
return old_dispatch(action);
}
};
}
const store = redux.createStore(reducer, redux.applyMiddleware(middleware));
store.subscribe(() => { console.log(store.getState()); });
store.dispatch({ type: 'inc' });
store.dispatch({ type: 'inc' });
store.dispatch({ type: 'dec' });
store.dispatch({ type: 'dec' });
// 运行结果
$ node a.js 130 ↵
/usr/local/lib/node_modules/redux/lib/redux.js:661
throw new Error(process.env.NODE_ENV === "production" ? formatProdErrorMessage(15) : 'Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.');
^
Error: Dispatching while constructing your middleware is not allowed. Other middleware would not be applied to this dispatch.
at dispatch (/usr/local/lib/node_modules/redux/lib/redux.js:661:15)
at dispatch (/usr/local/lib/node_modules/redux/lib/redux.js:667:28)
at middleware (/private/tmp/a.js:11:5)
at /usr/local/lib/node_modules/redux/lib/redux.js:671:16
at Array.map (<anonymous>)
at /usr/local/lib/node_modules/redux/lib/redux.js:670:31
at Object.createStore (/usr/local/lib/node_modules/redux/lib/redux.js:162:33)
at Object.<anonymous> (/private/tmp/a.js:19:21)
at Module._compile (node:internal/modules/cjs/loader:1101:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
思考题
下面middleware中的 dispatch,是怎么变成这个store的从头开始的 dispatch 的?
// 演示代码
function middleware({dispatch, getState}) {
return function dispatch_2_dispatch(old_dispatch) {
return function newdispatch(action) {
old_dispatch(action);
if (getState() == 2 && action.type === 'inc')
dispatch({ type: 'inc' });
if (getState() == 2 && action.type === 'dec')
dispatch({ type: 'dec' });
}
};
}
Redux-thunk
TypeScript源码
raw.githubusercontent.com/reduxjs/red…
// redux-thunk 源码
import type { Action, AnyAction } from 'redux'
import type { ThunkMiddleware } from './types'
export type {
ThunkAction,
ThunkDispatch,
ThunkActionDispatch,
ThunkMiddleware
} from './types'
/** A function that accepts a potential "extra argument" value to be injected later,
* and returns an instance of the thunk middleware that uses that value
*/
function createThunkMiddleware<
State = any,
BasicAction extends Action = AnyAction,
ExtraThunkArg = undefined
>(extraArgument?: ExtraThunkArg) {
// Standard Redux middleware definition pattern:
// See: https://redux.js.org/tutorials/fundamentals/part-4-store#writing-custom-middleware
const middleware: ThunkMiddleware<State, BasicAction, ExtraThunkArg> =
({ dispatch, getState }) =>
next =>
action => {
// The thunk middleware looks for any functions that were passed to `store.dispatch`.
// If this "action" is really a function, call it and return the result.
if (typeof action === 'function') {
// Inject the store's `dispatch` and `getState` methods, as well as any "extra arg"
return action(dispatch, getState, extraArgument)
}
// Otherwise, pass the action down the middleware chain as usual
return next(action)
}
return middleware
}
const thunk = createThunkMiddleware() as ThunkMiddleware & {
withExtraArgument<
ExtraThunkArg,
State = any,
BasicAction extends Action = AnyAction
>(
extraArgument: ExtraThunkArg
): ThunkMiddleware<State, BasicAction, ExtraThunkArg>
}
// Attach the factory function so users can create a customized version
// with whatever "extra arg" they want to inject into their thunks
thunk.withExtraArgument = createThunkMiddleware
export default thunk
抽取核心代码,得到
// redux-thunk 源码(精简版)
const middleware =
({ dispatch, getState }) =>
next =>
action => {
// The thunk middleware looks for any functions that were passed to `store.dispatch`.
// If this "action" is really a function, call it and return the result.
if (typeof action === 'function') {
// Inject the store's `dispatch` and `getState` methods, as well as any "extra arg"
return action(dispatch, getState, extraArgument)
}
// Otherwise, pass the action down the middleware chain as usual
return next(action)
}
把它变成function的函数形式,同时对比一下我们之前自己写的middleware:
// redux-thunk 源码(推演版)
function middleware({ dispatch, getState }) {
return function foo(next) {
return function bar(action) {
// The thunk middleware looks for any functions that were passed to `store.dispatch`.
// If this "action" is really a function, call it and return the result.
if (typeof action === 'function') {
// Inject the store's `dispatch` and `getState` methods, as well as any "extra arg"
return action(dispatch, getState, extraArgument)
}
// Otherwise, pass the action down the middleware chain as usual
return next(action)
}
}
}
// 中间件的一个例子
function middleware({dispatch, getState}) {
return function dispatch_2_dispatch(old_dispatch) {
return function newdispatch(action) {
old_dispatch(action);
if (getState() == 2 && action.type === 'inc')
dispatch({ type: 'inc' });
if (getState() == 2 && action.type === 'dec')
dispatch({ type: 'dec' });
}
};
}
可以知道源代码中的 next 就是 old_dispatch 的地位。foo 就是 dispatch_2_dispatch 的地位。bar 就是 newdispatch 的地位。
它做了什么呢?当 action 是 function (这时候就不是 PlainObject 了!)的时候,直接调用 action,而且斩断中间件调用链;否则就当作什么都没有发生。
注意当 action 是 function 的时候,action 的头两个参数是 dispatch 和 getState,这也算是 Redux-thunk 的设计约定了。
注意当 action 是function 时,它返回的是 action() 的返回值。如果这是个 promise,那么你就可以在外面 await 它。
例子
// 演示代码
const redux = require("redux")
const reducer = (state, action) => {
if (state == null) return 0;
if (action.type === 'inc') return state + 1;
if (action.type === 'dec') return state - 1;
return state;
}
const thunkmiddleware = require("redux-thunk").default
const store = redux.createStore(reducer, redux.applyMiddleware(thunkmiddleware));
store.subscribe(() => { console.log(store.getState()); });
store.dispatch({ type: 'inc' });
store.dispatch({ type: 'inc' });
store.dispatch({ type: 'dec' });
store.dispatch({ type: 'dec' });
store.dispatch(() => {
console.log("xxxxx");
})
store.dispatch((dispatch, getState) => {
console.log("yyyyy");
console.log(getState());
console.log("yyyyy");
dispatch({ type: 'dec' });
console.log("yyyyy");
})
// 运行结果
$ node a.js
1
2
1
0
xxxxx
yyyyy
0
yyyyy
-1
yyyyy
本质
我以为,它要做的事情的本质,是利用 redux 的框架,利用 dispatch(action) 的编程模型,做一些不改变 state 的事。
我们前面有个问题:监听event并触发一些不改变state的事 (比如发个心跳网络包出去)。利用 thunk 就大概可以解决。
那我的下一个问题就是:为什么要利用 redux 的框架来做这个事?我还没有一个特别说服人的答案。好处是,它可以统一整个代码的编码模型,统一利用 dispatch。
扩展用法:用来组合一套普通 action
如果你有几个 action,总是一起出现的,比如要把 state.a.b.c--,还要把 state.d.e.f++,那么就可以利用 thunk 把它们组合起来:
store.dispatch((dispatch, getState) => {
dispatch({ type: 'a.b.c.dec' });
dispatch({ type: 'd.e.f.inc' });
})
扩展用法:配合 Promise
利用如果 action 是个 function 时,它的返回值也是 dispatch 的返回值的特点。可以做这样的事:
await store.dispatch(async (dispatch, getState) => {
await new Promise(resolve => setTimeout(resolve, 2000));
})
store.dispatch({ type: 'a.b.c.dec' });
store.dispatch({ type: 'd.e.f.inc' });
或者干脆
await store.dispatch(async (dispatch, getState) => {
await new Promise(resolve => setTimeout(resolve, 2000));
dispatch({ type: 'a.b.c.dec' });
dispatch({ type: 'd.e.f.inc' });
})
后者是有实际价值的,比如去网络上获取一个东西,await 到它返回后,把这个值更新到 state 里去。