zoukankan      html  css  js  c++  java
  • Redux初探与异步数据流

    基本认知

    先贴一张redux的基本结构图
    redux
    原图来自《UNIDIRECTIONAL USER INTERFACE ARCHITECTURES》

    在这张图中,我们可以很清晰的看到,view中产生action,通过store.dispatch(action)将action交由reducer处理,最终根据处理的结果更新view。
    在这个过程中,action是简单对象,用于描述一个动作以及对应于该动作的数据。例如:

    const ADD_TODO = 'ADD_TODO';
    
    // action
    {
    	type: ADD_TODO,
    	data: 'some data'
    }  
    

    而reducer则是纯函数,且是幂等的,即只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。

    同步数据流

    在拥有了以上基本认知之后,我们来看下redux到底是如何工作的。Talk is cheap, show me the code.

    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={{'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。

    由于篇幅限制,以上代码未分模块

    下面是截图:
    效果图

    控制台打印输出:
    控制台打印

    从上面代码中可以清晰的看出,当用户点击“Click Me!”的时候,会立即调用createAction产生一个action,之后redux获取这个action并调用store.dispatch将这个action丢给reducer进行处理,demo中的reducer仅仅打印了action。
    数据从view中流出,经reducer处理后又回到了view。
    至此,我们看到的一切都是跟上面的基本认知是一致的。

    接下来说说异步数据流,这块也是困扰了我好久,直到最近才搞清楚内在原因。

    Redux Middleware

    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-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也只是知其然不知其所以然,很容易陷入一些通过样例代码理解出来的错误的概念中,后面再纠正就需要花费很多时间和精力了!

  • 相关阅读:
    java的访问控制修饰符
    js将一个数组分割成二维子数组
    java的数组
    Java 变量类型
    java
    java的 %和 ^
    Python第三方库 -> 由于是ipynb格式,所以没有对应的输出结果
    获取浏览器历史记录 browserhistory
    虚拟机 CentOS 6.8 安装
    解决 django.db.utils.OperationalError: (1051, "Unknown table 'mydorm.users_studen t'")
  • 原文地址:https://www.cnblogs.com/bingooo/p/5500108.html
Copyright © 2011-2022 走看看