先贴一张redux的基本结构图
原图来自《UNIDIRECTIONAL USER INTERFACE ARCHITECTURES》html
在这张图中,咱们能够很清晰的看到,view中产生action,经过store.dispatch(action)将action交由reducer处理,最终根据处理的结果更新view。
在这个过程当中,action是简单对象,用于描述一个动做以及对应于该动做的数据。例如:前端
const ADD_TODO = 'ADD_TODO'; // action { type: ADD_TODO, data: 'some data' }
而reducer则是纯函数,且是幂等的,即只要传入参数相同,返回计算获得的下一个 state 就必定相同。没有特殊状况、没有反作用,没有 API 请求、没有变量修改,单纯执行计算。react
在拥有了以上基本认知以后,咱们来看下redux究竟是如何工做的。Talk is cheap, show me the code.express
import React from 'react' import { createStore, bindActionCreators } from 'redux' import { connect } from 'react-redux' import ReactDom from 'react-dom' import { Provider } from 'react-redux' function createAction() { return { type: 'ADD_TODO', data: 'some data' } } class App extends React.Component { constructor() { super(); } render() { return ( <div style={{width:'200px', height:'200px',margin:'100px',border:'2px solid black'}}> <div onClick={this.props.actions.createAction.bind(this)}> {"Click Me!"} </div> </div> ); } } function mapStateToProps(state) { return { data: state } } function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({createAction}, dispatch) } } var AppApp = connect( mapStateToProps, mapDispatchToProps )(App); function reducer(state, action) { console.log(action); return state; } var store = createStore(reducer); ReactDom.render( <Provider store={store}> <AppApp /> </Provider>, document.getElementById('container') );
这是一个精简版本的redux demo,每点击一次“Click Me!”,控制台会打印一次action。json
因为篇幅限制,以上代码未分模块redux
下面是截图:
数组
控制台打印输出:
promise
从上面代码中能够清晰的看出,当用户点击“Click Me!”的时候,会当即调用createAction产生一个action,以后redux获取这个action并调用store.dispatch将这个action丢给reducer进行处理,demo中的reducer仅仅打印了action。
数据从view中流出,经reducer处理后又回到了view。
至此,咱们看到的一切都是跟上面的基本认知是一致的。app
接下来讲说异步数据流,这块也是困扰了我很久,直到最近才搞清楚内在缘由。dom
redux为咱们作了不少的事情,咱们均可以不用经过显示的调用dispatch函数就将咱们的action传递给reducer。这在前面的demo中就能够看到。可是至此,redux一直没有解决异步的问题。试想,若是我在页面输入一段内容,而后触发了一个搜索动做,此时须要向服务端请求数据并将返回的数据展现出来。这是一个很常见的功能,可是涉及到异步请求,刚刚的demo中的方法已经再也不适用了。那么redux是如何解决异步问题的呢?
没错,就是引入middleware。middleware,顾名思义就是中间件。用过express的同窗对中间件应该都很熟悉。其实在redux中,middleware并不只仅用于解决异步的问题,它还能够作不少其余的事情,好比记录日志、错误报告、路由等等。
关于redux middleware的说明在官方文档中已经有了很是清晰的说明,中文版和英文版都有,这里就不在赘述,只摘录一句话,说明以下。
It provides a third-party extension point between dispatching an action, and the moment it reaches the reducer.
这里我想说下redux middleware的具体实现,我也正是从源代码中找到了困扰个人问题的缘由。
先看applyMiddleware(...middlewares)的代码:
import compose from './compose' export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, initialState, enhancer) => { var store = createStore(reducer, initialState, enhancer) var dispatch = store.dispatch var chain = [] var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
代码很短,此处咱们只关注最内层函数的实现。在建立了store之后,咱们对传进来的每个middleware进行以下处理:
var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } chain = middlewares.map(middleware => middleware(middlewareAPI))
处理后获得一个数组保存在chain中。以后将chain传给compose,并将store.dispatch传给返回的函数。那么在这里面作了什么呢?咱们再看compose的实现:
export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } else { const last = funcs[funcs.length - 1] const rest = funcs.slice(0, -1) return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args)) } }
compose中的核心动做就是将传进来的全部函数倒序(reduceRight)进行以下处理:
(composed, f) => f(composed)
咱们知道Array.prototype.reduceRight是从右向左累计计算的,会将上一次的计算结果做为本次计算的输入。再看看applyMiddleware中的调用代码:
dispatch = compose(...chain)(store.dispatch)
compose函数最终返回的函数被做为了dispatch函数,结合官方文档和代码,不可贵出,中间件的定义形式为:
function middleware({dispatch, getState}) { return function (next) { return function (action) { return next(action); } } } 或 middleware = (dispatch, getState) => next => action => { next(action); }
也就是说,redux的中间件是一个函数,该函数接收dispatch和getState做为参数,返回一个以dispatch为参数的函数,这个函数的返回值是接收action为参数的函数(能够看作另外一个dispatch函数)。在中间件链中,以dispatch为参数的函数的返回值将做为下一个中间件(准确的说应该是返回值)的参数,下一个中间件将它的返回值接着往下一个中间件传递,最终实现了store.dispatch在中间件间的传递。
看了中间件的文档和代码以后,我算是搞明白了中间件的原理。以前一直困扰个人问题如今看来实际上是概念问题(此处不提也罢),中间件只关注dispatch函数的传递,至于在传递的过程当中干了什么中间件并不关心。
下面看看经过中间件,咱们如何实现异步调用。这里就不得不提redux-thunk中间件了。
redux与redux-thunk是同一个做者。
咱们知道,异步调用何时返回前端是没法控制的。对于redux这条严密的数据流来讲,如何才能作到异步呢。redux-thunk的基本思想就是经过函数来封装异步请求,也就是说在actionCreater中返回一个函数,在这个函数中进行异步调用。咱们已经知道,redux中间件只关注dispatch函数的传递,并且redux也不关心dispatch函数的返回值,因此只须要让redux认识这个函数就能够了。
看了一下redux-thunk的源码:
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作了一下以下判断:
if (typeof action === 'function') { return action(dispatch, getState, extraArgument); }
也就是说,若是发现actionCreater传过来的action是一个函数的话,会执行一下这个函数,并以这个函数的返回值做为返回值。前面已经说过,redux对dispatch函数的返回值不是很关心,所以此处也就无所谓了。
这样的话,在咱们的actionCreater中,咱们就能够作任何的异步调用了,而且返回任何值也无所谓,因此咱们可使用promise了:
function actionCreate() { return function (dispatch, getState) { // 返回的函数体内自由实现。。。 Ajax.fetch({xxx}).then(function (json) { dispatch(json); }) } }
经过redux-thunk,咱们将异步的操做融合进了现有的数据流中。
最后还须要注意一点,因为中间件只关心dispatch的传递,并不限制你作其余的事情,所以咱们最好将redux-thunk放到中间件列表的首位,防止其余中间件中返回异步请求。
以上是最近一段时间学习和思考的总结。在这期间发现,学习新知识的基础是要把概念理解清楚,不能一味的看样例跑demo,不理解概念对demo也只是知其然不知其因此然,很容易陷入一些经过样例代码理解出来的错误的概念中,后面再纠正就须要花费不少时间和精力了!