< Back

redux源码梳理

createStore

function createStore(reducer, preloadedState, enhancer)

一个reducer就是一个接受一个state和一个action,返回一个新的state的纯函数。preloadedState用于初始化状态。当调用createStore的时候会创建一个对象包含如下方法

return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable }

主要维护如下数据:reducer,state,listeners。

当调用subscribe(listener)的时候,将listener加入到listeners中。

nextListeners.push(listener)

当调用dispatch(action)的时候,执行执行传入的reducer来得到新状态

currentState = currentReducer(currentState, action)

以及遍历执行listeners,

for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() }

到目前为止感觉没有什么特别的,主要还是一个监听者模式的感觉。

applyMiddleware

中间件这里大概是整个redux最难理解的部分了。

看到可以像下面这样创建,想了一下,不是createStore的第三个参数enhancer才是放中间间的吗?怎么直接放第二个了。

var store = Redux.createStore(counter, Redux.applyMiddleware(logger1, logger2, logger3))

createStore一开始就会判断,如果第二个参数为函数且第三个参数没有设置,那么就把第二个参数当中间间用,注意这里下面这里直接return了,也就是说下面那些代码暂时(createStore的主要逻辑)都不会执行了,而是把createStore闭包给enhancer,让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.') } return enhancer(createStore)(reducer, preloadedState) }

那么再看下这个enhancer也就是传入的Redux.applyMiddleware(logger1, logger2, logger3)是什么,我的感受是大量闭包的使用。

一个中间件大概长这样

function logger1({ getState }) { return next => action => { const returnValue = next(action) return returnValue } }

applyMiddleware的代码非常简短,创建store,并将getState分配给每个中间件,使得每个中间间在执行的时候都可以访问到当前状态,然后通过compose将每个中间件连接起来,达到按照中间件参数顺序,从左到右依次执行的效果。

function applyMiddleware(...middlewares) { 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.' ) } const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args) } const chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }

compose更加精简了, 通过reduce将函数一个个包起来,源码中也在返回结果那注释了

A function obtained by composing the argument functions from right to left. For example, compose(f, g, h) is identical to doing (...args) => f(g(h(...args))).

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))) }

而要更好的理解这里,可以直接拿上一个真正的中间件来看,比如上面的logger1,传入compose的chain为下面这个函数的一个数组。也就是reduce在进行遍历时候的元素,a(b(...args))实际上就是给每个中间件闭包next,实现了传说中的中间件洋葱模型

next => action => { const returnValue = next(action) return returnValue }

那有一个问题,那最里面那层的next是啥呢?想象一下中间件都跑完了接下来要干嘛呢?要把action传递给reducer来更新状态啊!所以他这里最后是返回一个函数(...args) => a(b(...args)),目的就是为了给最里面那层的中间件设置next,这个next也就是传递action给reducer的dispatch函数。

dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch }

之所以叫enhancer,增强器就是增强了dispatch,外部调用的dispatch实际上不是store.dispatch,而是经过compose之后的dispatch。

看完我觉得使用时要注意的大概是每个中间件记得一定要调用next。而这种将调用时机交给用户的做法,可以让用户拥有更高的自由度。

然后顺带一提下面这段代码,这里他说的constructing时期就是middlewares.map(middleware => middleware(middlewareAPI))的时候,因此在中间件的最外层函数那里不允许调用dispatch。

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) }

那为什么里层的函数又可以调用dispatch呢?因为这里用的是let,map结束后dispatch被重新赋值了。这里我一开始不是很理解的是,闭包之后dispatch还能受到外部影响而改变?测试了一下如果这里写成dispatch: dispatch而不是返回一个函数的形式的话,dispatch是不能被改变的。

总而言之,第一层函数(也就是构建阶段)不允许调用dispatch,里层的因为被dispatch被重新赋值了所以可以调用,且是增强后的dispatch。

combineReducers

接收的是一个包含多个reducer的对象,像下面这样的:

const reducer = Redux.combineReducers({ todos, counter });

返回的是一个combination函数: combination(state = {}, action),接口保持和一般reducer统一。combination主要逻辑是下面这段,挨个执行每个reducer,每个reducer的结果存放在nextState中,通过每个reducer的新结果和旧结果对比来判断状态有无变化,只要有一个reducer返回了与之前不同的结果就返回nextState作为新状态,否则返回老状态。

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 } hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length return hasChanged ? nextState : state

bindActionCreators

这个主要是为了方便而编写的一个工具,通常我们这样来dispatch一个action

store.dispatch({ type: 'INCREMENT' })

有个bindActionCreators后可以将所有的action都合并到一起,并传给bindActionCreators

const actionCreators = { 'INCREMENT': function(){ return { type: 'INCREMENT', }; }, 'DECREMENT': function(){ return { type: 'DECREMENT', }; } } const boundActionCreators = Redux.bindActionCreators(actionCreators, store.dispatch)

然后可以这样调用

boundActionCreators.INCREMENT() boundActionCreators.DECREMENT()

这样不用手动去写type的字符串,还能获得编辑器的提示,提高开发效率,将action都归到一起也更便于维护。

原理也很简单,就是将dispatch闭包进去,然后返回一个调用dispatch的函数。

function bindActionCreator(actionCreator, dispatch) { return function() { return dispatch(actionCreator.apply(this, arguments)) } }

redux-thunk

thunk的代码非常简单,通常用他是用来处理异步请求,这里仅仅只是判断了一下action是否为function,是的话就执行这个action,否则next。

function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => (next) => (action) => { if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } return next(action); }; } const thunk = createThunkMiddleware(); thunk.withExtraArgument = createThunkMiddleware; export default thunk;

这个action不是函数的时候通常就是最传入了一个plain object的时候,这个时候他直接去next(action),上面提到过,最后一个中间件的next其实就是store.dispatch,因此当只有thunk这一个中间件的时候,这里的next(action)就是直接将action交给reducer了。

而当action是函数的时候,这个action通常就是我们的异步函数,我在这个函数里获取异步数据,然后,使用dispatch继续分发action,此处的dispatch因为是增强的dispatch,所以,代码会再次进到thunk的这个中间件里来做判断,直到收到一个非函数的action。