建议:有 redux 的实践后再来看相关的文章。你需要先知道 redux 能让你做什么,才会激起对源码的欲望。
- 推荐看看这篇文章 Redux 卍解,回顾一下 redux都给你提供了哪些 api ,能干些什么。
- 不准备把行行代码都贴出来,建议自行打开源码同步阅读。
redux 的源码内容并不多,可以说很少,相比 koa.js 会多一点 (笑)。源码结构如下图:
combineReducers.js
combineReducers的大致结构如下
回忆我们使用
combineReducers
的时候
//a.reducer
const aR = function(state = initialState, action) {
switch (action.type) {
case ...
default:
return state;
}
}
//b.reducer
const bR = function(state = initialState, action) {
switch (action.type) {
case ...
default:
return state;
}
}
export default combineReducers({aR,bR});
对应到源码,就不难理解使用时的一些“要求”。(比如 接受的参数对象 的 key 要与对应的 state 对应。)
代码先对 参数 reducers 进行一次遍历查空,值非空且为 function 的键值对组成最终的 recuder。题外话:现在遍历对象还是得靠 Object.keys 吗?
//assertReducerShape
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (process.env.NODE_ENV !== 'production') {
if (typeof reducers[key] === 'undefined') {
warning(`No reducer provided for key "${key}"`)
}
}
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
按部就班下来,finalReducers 就是我们刚才得到的新对象,去往 assertReducerShape 内部,可以发现该函数内并没有做影响外部的事情,也没有返回值,该函数的用意应该是试探性的运行当前的 reducer,确定 reducer 被调用后没有返回 undefined ..而且还进行了两次判断...@todo 暂时还未明白为何要两次判断
let shapeAssertionError
try {
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
combineReducers
的返回值仍然是函数,毕竟我们只是将多个 reducer 函数合并成一个函数;核心逻辑代码如下,可以看出 进入 combineReducers 的 reducers 的 key 是需要和 state 下对应 key 一致的;赋值给 nextState 前还需要判断非 undefined ,同时只要任一的 reducer 改变了 state,hasChanged 的值都为true,都将返回新的 state。
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)
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
其实核心逻辑的代码量可能还不如判空,判 undefined ,输出警告等行为的代码量,但是一个优秀的插件,对不合理的值应该有合适的处理而不是等着运行环境给我们报错,这是很多新手前端(比如我)会忽视的内容,日常可能还得靠各种报错一点点累计经验...以至于不会重视报错,认为有错改一下就好了。
bindActionCreators.js
这个函数我们经常用...但你可能并不太了解他的作用:
const mapStateToProps = state => {
return {
stateKey: state.stateKey
};
};
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
oneAction
},
dispatch
);
export default connect(mapStateToProps, mapDispatchToProps)(Component);
以前的认知就是执行了这些,就可以在组件下直接 this.props.oneActionCreator(actionParam)
来发起一个 action,而不是 this.store.dispatch(oneActionCreator(actionParam))
。
我们先从源码的返回值 开始看,如下图。
函数返回 boundActionCreators
我们再追溯的看这个变量如何赋值的。
const boundActionCreators = {}
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
可以大致得知,函数主要做了这样的操作。
那关键步骤就是bindActionCreator
了,而其也十分简单...
function bindActionCreator(actionCreator, dispatch) {
return function() {
return dispatch(actionCreator.apply(this, arguments))
}
}
朋友们还记得 dispatch
做了什么吗~ 分发action!没错,bindActionCreator直接帮你在创建 action 时 dispatch 这个 action。
综述,bindActionCreators.js 的处理过程如下图。
可是这里为什么要使用 apply 呢?为什么不直接
return dispatch(actionCreator(arguments))
?我们知道以函数形式调用的函数的 this 的值是全局对象 ( javascript 权威指南第八章),使用 apply 后间接等于方法调用,这里形如 this. actionCreator(arguments)
,方法形式调用的函数其上下文 (this) 为调用的对象。所以这里的 this
是打算将调用 bindActionCreator
内部函数的上下文传递给 actionCreator
使用。
applyMiddleware.js
该模块返回函数 applyMiddleware ,参数 ...middlewares
类似于用 arguments 来接受一系列参数。函数内部返回值仍然是函数。注意 返回的值 是用于 createStore 的第三个参数 enhancer。
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = () => {
throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`
)
}
let chain = []
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
箭头函数真的不太适合阅读...(可能是我不太习惯)
return function (createStore) {
return function (...args) {
const store = createStore(...args)
...
}
}
符合 enhancer 在 createStore 的逻辑enhancer(createStore)(reducer, preloadedState)
观察函数的返回值,可以得知函数内部只改动了 dispatch 方法,再看源码。
dispatch = compose(...chain)(store.dispatch)
compose 方法是另一个文件导入的,源码也不多,比较关键的一句是 return funcs.reduce((a, b) => (...args) => a(b(...args)))
,但是这一句让不熟悉函数式编程的我思考了很久......
compose(...chain)
,输出这一句的执行结果
function (...args){
return a( b(...args) )
}
看起来只是个普通的函数,只是这里的 a ,b 是谁呢?ಥ_ಥ
我们知道 reduce 返回值是最后一次累加计算的结果,compose(...chain)
的返回值仍然是个函数,函数内的 a 就是上一次累加返回的结果,b 是当前项,也就是 chain 的最后一项;上一次累加结果仍然是 这个普通的函数,只不是此时的 b 是 chain 的倒数第二项,a 是再上一次的累加返回结果...
dispatch = compose(...chain)(store.dispatch)
此时再看这一句,其实就是:先以参数,运行了 chain 的最后一项函数,返回值做为参数被 a 调用,a是上一次的返回值,再次跳回 2 行,此时的 b 是 chain 的倒数第二项...以此类推,到chain 的第二项时(reduce 没有 设置初始值时,accumulator 的默认值为调用数组的第一项,同时从第二项开始累加计算),以 chain 第三项运行的返回值做为参数,调用 第二项,其后的返回值,做为参数 调用 chain 的第一项,然后 执行 3行,再次跳回3 行 ... 返回值 赋给 dispatch。
// funcs 是该函数的参数数组。
return funcs.reduce((a, b) => (...args) => a(b(...args)))
//可以转换成
0 return funcs.reduce( function (a,b) {
1 return function (...args){
2 return a( b(...args) )
3 }
4 })
总结下来就是...
假设数组 chain 是 [ fun1, fun2, fun3]
,相当于最后以
fun1(fun2(fun3(store.dispatch)))
这样的形式调用了。
写到这里时我并不是十分了解中间件的作用,不过从此处的代码可以推断,中间件需要接受一个形如 middlewareAPI 的参数,且返回值仍然是函数,返回值很有可能仍然是 store.dispatch...
let chain = []
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
也就是说中间件主要对 store.dispatch 进行一些改造,同时也拿了 getState 方法做一些事情。
此时在看 applyMiddleware 的用法,至少不会觉得茫然吧...(大概 ಠ౪ಠ)
const enhancer = applyMiddleware(
thunk,
createLogger()
);
const store = createStore(rootReducer, initialState, enhancer);
初学 redux 我觉得最困难的就是记很多名词,很多规矩,redux 规定了很多东西,让我摸不着头脑。为此我送上一份源码脉络梳理图。
最后送上一份源码脉络梳理图,画的有些粗略... 本意就是希望看到图还能回忆起各个函数大概做了些什么事情...