阅读 redux 源码收获 有一个 NB 的程序猿哥哥说过,作为发开人员,不是说会用很多东西工具、框架就很厉害,而是了解框架的设计思想,并且自己有能力设计一款 NB 的框架,这样的开发才是牛逼的。
开始正事。
redux 分为几个部分,从简单到难(我以为的简单到难。>_<)说一下:
compose.js : 正如字面意思,组合。将多个函数组合起来,比如将后面函数的返回值作为参数传递给前面一个函数。我之前学习的时候写过 compose 这种函数,但思路不同,同样也比 redux 中的实现更复杂(但是必须承认的是,redux 的更棒)。可以看一下我的 compose.js 和它的,
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 var toArray = require ('./util.js' ).toArray ;module .exports = function ( ) { var funs = toArray (arguments ); return function ( ) { var result = args = toArray (arguments ); result = funs[0 ](...result); for (var i = 1 , len = funs.length ; i < len; ++i) { result = funs[i](result) } return result; } } 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))) }
这个函数主要用在组合中间件上。
bindActionCreators :这个方法用于将很多 actions 的函数放置于一个对象中,方便调用。值得注意的是代码中对类型做了很多校验,不符合的都 throw new Error 出去。
combineReducers :接收一个对象,将所有 key 组合起来,返回一个可以返回该数据层的 reducer 函数。这个数据层不简单。这不是一个简单的概念,而是涉及到 redux 的设计理念:合理的 state 结构。一个 state 树可以有很多枝吧?比如现在我的 state 树是这样的:
1 2 3 4 5 6 7 8 9 10 11 export default combineReducers ({ data : function (state = {}, action ){ switch (action.type ) { case FETCH_FIRST_DATA : return Object .assign ({}, state, {...action.data }); default : return state; } } });
我想新建一个分支,用于表示单独的数据,比如我表单需要提交的数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const commitReducer = { commit : combineReducers ({ selectedRoomNumber : roomNumberCommit, phoneNo, accommodationList, arriveTime }) }; export default combineReducers ({ data : function (state={}, action ){ switch (action.type ) { case FETCH_FIRST_DATA : return Object .assign ({}, state, {...action.data }); default : return state; } }, ...commitReducer });
这样很清楚:在任意数据的任意分层都可以聚合所有该层的 reducer 为一个包含该层所有情况的 reducer。在每次 dispatch 的时候,都会调用这个最根部的 reducer,由这个最根部的 reducer 层层调用各 reducer(具体实现不是表面上的递归,而是常规的 js 调用,可以这样理解,调用顶层 reducer 函数,返回一个对象表示 state 对象树),比如
1 2 3 4 5 6 7 8 9 10 11 const getData = { type : 'GET_FIRST_DATA' }; store.dispatch (getData); { data : (function data (state = {}, action ))(previousState.data , getData), commit : (function commit (state, action ){})(previousState.commit , getData) }
是的,想想就知道代码并不难。是的,实际代码也不难,100 多行代码,而且还是大面积类型检测代码的情况下。但是,这种思想是值得学习的。为开发人员设计这样的功能,确实很好。根据这样的思想来设计 state tree 也很方便。
createStore :这个是倒数第二个源码文件。说明还不是最难的。功能如其名,创建 store,使用闭包存储 state 和 reducer。暴露出如下几个 API:
1 2 3 4 5 6 7 { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable }
通过代码的设计,发现几处有意思的东西:redux 给 redux 使用者进行了代码测试,会自动派发一些随机事件,看是否返回了原来的 state,没有的话,就报错,提示用户。其次就是对外提供接口 enhancer,这个也是 applyMiddleware 开始介入的地方。
applyMiddleware :这个是最绕的东西。毕竟是多层高阶函数,不仔细看看还是会晕的。整体来说这个函数就是:
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 applyMiddleware = (...middleares ) => 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 }; } 对任意一个中间来说,其流程是: middlewares = ({ dispatch, getState } ) => next => action => something-interesting
最开始我一直没搞懂 dispatch,为啥明明是不准使用的,还能用?后来想通了,js 中的函数是可以改变指向的,如 #3 就将 #2 的 dispatch 改为增强的新函数。其次是 next 函数,next 函数可以在 #3 中看出是原始的 store.dispatch 函数,也就是光秃秃的 dispatch 函数,这个函数能执行同步的函数。我们来看看一个常见的 thunk 中间件是怎么撸的:
1 2 3 4 export function thunk ({ dispatch, getState } ) { return next => action => typeof action === 'function' ? action (dispatch, getState) : next (action) }
判断 action 是不是函数,是函数的话,执行函数,并且把 dispatch/getState 传入执行,这个 dispatch 是增强的版本,表明在 action 中运行的结果可以用增强的 dispatch 再处理一哈。一般来说我们是请求 api 的时候用 thunk,写一下:
1 2 3 4 5 6 7 8 9 export const FETCH_FIRST_DATA = 'FETCH_FIRST_DATA' ;export const getFirstData = id => async (dispatch, getState, axios) => { const ret = await axios.get (`http://jsonplaceholder.typicode.com/users/` ); return dispatch ({ type : FETCH_FIRST_DATA , data : ret.data }); };
先执行这个异步函数,执行好了再用增强的 dispatch 来执行一哈正常的 action 对象。
鄙人的理解是:next 这个参数一点都不语义化,我 jio 得叫一个 finalDispatch 会更亲切。因为不管再怎么增强这个 dispatch,其结果也只是为了最终的 action 对象提供数据而已,叫 next,总让我想起中间件,以为是该调用下一个中间件了。但看了代码,我 jio 得并不是调用下一个中间件的问题。我说的对吗?