基本认知

先贴一张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={{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。

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

下面是截图:

控制台打印输出:

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

Redux初探与异步数据流的更多相关文章

  1. angular之Rxjs异步数据流编程入门

    Rxjs介绍 参考手册:https://www.npmjs.com/package/rxjs 中文手册:https://cn.rx.js.org/ RxJS 是 ReactiveX 编程理念的 Jav ...

  2. Angular RxJs:针对异步数据流编程工具

    一. RxJs:针对异步数据流编程工具 1. 创建subject类对象(发送方) 2. subject.subscribe(观察者); (注册观察者对象observer,可以注册多个相当于回调函数取数 ...

  3. Redux学习笔记--异步Action和Middleware

    异步Action 之前介绍的都是同步操作,Redux通过分发action处理state,所有的数据流都是同步的,如果需要一步的话怎么办? 最简单的方式就是使用同步的方式来异步,将原来同步时一个acti ...

  4. Redux学习(2) ----- 异步和中间件

    Redux中间件,其实就是一个函数, 当我们发送一个action的时候,先经过它,我们就可以对action进行处理,然后再发送action到达reducer, 改变状态,这时我们就可以在中间件中,对a ...

  5. 【React全家桶入门之十三】Redux中间件与异步action

    在上一篇中我们了解到,更新Redux中状态的流程是这种:action -> reducer -> new state. 文中也讲到.action是一个普通的javascript对象.red ...

  6. 从Flux到Redux详解单项数据流

    从Flux到Redux是状态管理工具的演变过程,但两者还是有细微的区别的.但是最核心的都还是观察者模式的应用. 一.Flux 1. Flux的处理逻辑 通俗来讲,应用的状态被放到了store中,组件是 ...

  7. redux有价值的文档

    使用 Redux 管理状态,第 1 部分 https://www.ibm.com/developerworks/cn/web/wa-manage-state-with-redux-p1-david-g ...

  8. 3.4 redux 异步

    在大多数的前端业务场景中,需要和后端产生异步交互,在本节中,将详细讲解 redux 中的异步方案以及一些异步第三方组件,内容有: redux 异步流 redux-thunk redux-promise ...

  9. React Hooks实现异步请求实例—useReducer、useContext和useEffect代替Redux方案

    本文是学习了2018年新鲜出炉的React Hooks提案之后,针对异步请求数据写的一个案例.注意,本文假设了:1.你已经初步了解hooks的含义了,如果不了解还请移步官方文档.(其实有过翻译的想法, ...

随机推荐

  1. 19SpringMvc_在业务控制方法中收集List集合中包含JavaBean参数

    本文要实现的功能是给一张表单:

  2. jacob下载问题, Office word 此文件正由另一应用程序或用户使用的解决方法

    http://jingyan.baidu.com/article/75ab0bcbd6682fd6864db2db.html

  3. JS调用Silverlight方法拾遗

    在最近做的物联网项目中,需要利用封装过的Silverlight刻度控件显示温度,湿度,二氧化碳浓度等值.由于最新的数据是通过js ajax获取的,所以需要把这些数据传递给silverlight显示,这 ...

  4. [CareerCup] 11.2 Sort Anagrams Array 异位词数组排序

    11.2 Write a method to sort an array of strings so that all the anagrams are next to each other. 这道题 ...

  5. Week2学习过程报告

    一.学习内容 1. 熟悉Linux系统下的开发环境   2. 熟悉vi的基本操作   3. 熟悉gcc编译器的基本原理   4. 熟练使用gcc编译器的常用选项   5 .熟练使用gdb调试技术    ...

  6. Unity导出的Xcode项目,iOS端管理摄像头的方法

    Vuforia导出的工程中管理摄像头问题 在以前的篇幅中提到了unity端和iOS端的动态交互.现在出现了一个问题.因为设备上的摄像机是实例化过来的.并且是一个单例.unity虽然已经不再显示了.但是 ...

  7. 高校手机签到系统——第一部分Authority权限系统(下)

    很抱歉,之前寝室光纤断了,所以到现在才更新这个系列的第二篇博客.点击访问高校手机签到系统——第一部分Authority权限系统(上) 这几天我反思了一下上一篇写博上的方式,一味的贴代码式的,是否应该更 ...

  8. 一个简单js select插件

    现在,通过一个select插件,来介绍一下js插件的构建过程. 1.先上效果图 2.目录构建 (1)这个select插件,我给它起名交hongselect,所以呢,首先建个hongselect的文件夹 ...

  9. 一种仿照Asp.net Mvc思维构建WebSocket服务器的方法

    问题场景 Asp.net Mvc提供了DependencyResolver.Routing.Filter. Modelbinder等webForm所没有新概念,提高Web服务编写的便利性,记得很久之前 ...

  10. IDL简介与corba入门案例

    IDL接口定义语言简介   IDL用中立语言的方式进行描述,能使软件组建(不同语言编写的)间相互通信. IDL提供了一个桥来连接不同的系统. Corba 上的服务用IDL描述,将被映射为某种程序设计语 ...