redux的源码中,有6个js文件,分别是:
index.js
createStore.js
combineReducers.js
bindActionCreators.js
compose.js
applyMiddleware.js
我们一个一个来分析吧~
index 这里呢没有太多需要讲的,就是暴露了5个核心的api,分别是:
createStore:接收state和reducer,生成一颗状态树store
combineReducers:把子reducer合并成一个大reducer
bindActionCreators:把actionCreators和dispatch封装成一个函数,也就是把他们两绑定在一起
applyMiddleware:这是一个中间件
compose:一个组合函数
createStore 首先,定义初始化的action
1 2 3 exportconst ActionTypes = { INIT: '@@redux/INIT' }
这个createStore函数,会传入三个参数和一个返回值,分别是:
1、 @param {Function} reducer
这个reducer是一个函数,这个函数接收state和action,作一系列计算之后返回一个新的state。这里就体现了函数式编程的一些特性:
第一,这个reducer是一个纯函数,纯函数的特点是:对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态。不理解纯函数的筒子们,可以上网搜索一下。
第二,state是不可变的,我们这里对state作的一些修改和计算,不是直接修改原来的数据,而是返回修改之后的数据,原来的数据是保持不变。这里可以衍生到immutable,可以使用immutable和redux搭配使用。
2、@param {any} [preloadedState]
这是初始化的state,不多说。
3、@param {Function} [enhancer]
这个enhancer其实就是一个中间件,它在redux3.1.0之后才加入的。相当于把store做一些增强处理,让store更强大,功能更丰富,在之后的applyMiddleware那里会详细说的。这里也体现了高阶函数的思想,就像react-redux的connect方法一样,做一些包装处理之后,再返回。
4、@returns {Store}
这是返回的值,返回的是一棵状态树,也就是store啦。
这是做的源码分析,都写在注释里了。createStore返回的最常用的三个api是dispatch,subscribe,getState,一般我们只要传入reducer和preloadedState,就可以直接调用这三个方法,非常方便。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 exportdefaultfunctioncreateStore(reducer, preloadedState, enhancer) { if (typeof preloadedState === 'function' && typeof enhancer === 'undefined' ) { enhancer = preloadedState preloadedState = undefined } if (typeof enhancer !== 'undefined' ) {if (typeof enhancer !== 'function' ) {thrownewError('Expected the enhancer to be a function.' ) } return enhancer(createStore)(reducer, preloadedState) } if (typeof reducer !== 'function' ) {thrownewError('Expected the reducer to be a function.' ) } let currentReducer = reducer let currentState = preloadedStatelet currentListeners = [] let nextListeners = currentListeners let isDispatching = false functionensureCanMutateNextListeners() { if (nextListeners === currentListeners) { nextListeners = currentListeners.slice() } } functiongetState() { return currentState } functionsubscribe(listener) { if (typeof listener !== 'function' ) {thrownewError('Expected listener to be a function.' ) } let isSubscribed = true ensureCanMutateNextListeners() nextListeners.push(listener) returnfunctionunsubscribe() { if (!isSubscribed) {return } isSubscribed = false ensureCanMutateNextListeners() const index = nextListeners.indexOf(listener) nextListeners.splice(index, 1 ) } } functiondispatch(action) { if (!isPlainObject(action)) {thrownewError( 'Actions must be plain objects. ' +'Use custom middleware for async actions.' ) } if (typeof action.type === 'undefined' ) {thrownewError( 'Actions may not have an undefined "type" property. ' +'Have you misspelled a constant?' ) } if (isDispatching) {thrownewError('Reducers may not dispatch actions.' ) } try { isDispatching = true currentState = currentReducer(currentState, action) } finally { isDispatching = false } const listeners = currentListeners = nextListenersfor (let i = 0 ; i < listeners.length; i++) {const listener = listeners[i] listener() } return action } functionreplaceReducer(nextReducer) { if (typeof nextReducer !== 'function' ) {thrownewError('Expected the nextReducer to be a function.' ) } currentReducer = nextReducer dispatch({ type : ActionTypes.INIT }) } functionobservable() { const outerSubscribe = subscribereturn { subscribe(observer) { if (typeof observer !== 'object' ) {thrownewTypeError('Expected the observer to be an object.' ) } functionobserveState() { if (observer.next) { observer.next(getState()) } } observeState() const unsubscribe = outerSubscribe(observeState)return { unsubscribe } }, [$$observable]() { returnthis } } } dispatch({ type : ActionTypes.INIT }) return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable } }
combineReducers combineReducers的作用是将之前切分的多个子reducer合并成一个大的reducer,也就是说将很多的小状态树合并到一棵树上,整合成一棵完整的状态树。
这个函数接受一个参数,返回一个函数
1、@param {Object} reducers
这里接收多个reducer,传入的是一个对象
2、@returns {Function}
combineReducers的整个执行过程就是:将所有符合标准的reducer放进一个对象中,当dispatch一个action的时候,就遍历每个reducer,来计算出每个reducer的state值。同时,每遍历一个reducer,就判断新旧state是否发生改变,来决定是返回新state还是旧state,这是做的一个优化处理。
源码分析如下,前面还有一部分是一些error信息和warning信息的处理,就没有放进来了,感兴趣的话可以自己去看一下完整的源码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 exportdefaultfunctioncombineReducers(reducers) { 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] } } const finalReducerKeys = Object .keys(finalReducers)let unexpectedKeyCacheif (process.env.NODE_ENV !== 'production' ) { unexpectedKeyCache = {} } let shapeAssertionErrortry { assertReducerShape(finalReducers) } catch (e) { shapeAssertionError = e } returnfunctioncombination(state = {}, action) { if (shapeAssertionError) {throw shapeAssertionError } if (process.env.NODE_ENV !== 'production' ) {const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)if (warningMessage) { warning(warningMessage) } } 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)thrownewError(errorMessage) } nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey } return hasChanged ? nextState : state } }
bindActionCreators bindActionCreators的作用是:将action与dispatch函数绑定,生成可以直接触发action的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 functionbindActionCreator(actionCreator, dispatch) { return (...args ) => dispatch(actionCreator(...args))} exportdefaultfunctionbindActionCreators(actionCreators, dispatch) { if (typeof actionCreators === 'function' ) {return bindActionCreator(actionCreators, dispatch) } if (typeof actionCreators !== 'object' || actionCreators === null ) {thrownewError( `bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators} . ` +`Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?` ) } const keys = Object .keys(actionCreators)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) } } return boundActionCreators}
compose compose叫做函数组合,是一个柯里化函数,将多个函数合并成一个函数,从右到左执行。这同时也是函数式编程的特性。这个函数会在applyMiddleware中用到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 exportdefaultfunctioncompose(...funcs) { if (funcs.length === 0 ) {returnarg => arg } if (funcs.length === 1 ) {return funcs[0 ] } return funcs.reduce((a, b ) => (...args) => a(b(...args)))}
这一段代码的主要难点是在最后那一句,着重说一下reduce这个方法。这个reduce不是之前的reducer,这里的reduce函数是es5的一个归并数组的方法,是从数组的第一项开始,逐个遍历数组的所有项。
它接收两个参数,一个是在每一项上调用的函数,还有一个可选参数,是作为归并基础的初始值。调用的那个函数又接收四个参数,前一个值,当前值,项的索引,和数组对象。这个函数返回的任何值都会作为第一个参数自动传递给下一项。这样说可能比较抽象,举个例子:
1 2 3 [1 ,2 ,3 ,4 ,5 ].reduce((prev, cur ) => { return prev + cur })
用reduce就可以很快的求的数组所有值相加的和。另外,还有一个reduceRight()方法,跟reduce是一样的,只不过是从数组的右边开始遍历的。
我们回到源码上面return funcs.reduce((a, b) => (...args) => a(b(...args))),这其实就是遍历传入的参数数组(函数),将这些函数合并成一个函数,从右到左的执行。这就是中间件的创造过程,把store用一个函数包装之后,又用另一个函数包装,就形成了这种包菜式的函数。
applyMiddleware applyMiddleware是用来扩展redux功能的,主要就是扩展store.dispatch的功能,像logger、redux-thunk就是一些中间件。
它的实现过程是:在dispatch的时候,会按照在applyMiddleware时传入的中间件顺序,依次执行。最后返回一个经过许多中间件包装之后的store.dispatch方法。
如果理解了之前说的compose函数,这一段代码应该也很容易就能看懂啦。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 exportdefaultfunctionapplyMiddleware(...middlewares) { return (createStore ) => (reducer, preloadedState, enhancer) => {const store = createStore(reducer, preloadedState, enhancer)let dispatch = store.dispatchlet chain = []const middlewareAPI = { getState: store.getState, dispatch: (action ) => dispatch(action) } chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
总结 整个的源码就全部分析完了,我们可以看到,redux的源码很多地方都体现了函数式编程的思想。函数式编程写出来的代码确实很漂亮很简洁,但是理解起来也比较困难。这也只是函数式编程的很小一部分,有兴趣的话可以去了解一下其他的部分。
写到这里也差不多了,希望以后有机会能多看点源码,get一些新的知识,最后感谢宋老师的宝贵意见,bye