Redux 入门教程(二):中间件与异步操作
上一篇文章,我介绍了 Redux 的基本做法:用户发出 Action,Reducer 函数算出新的 State,View 重新渲染。
但是,一个关键问题没有解决:异步操作怎么办?Action 发出以后,Reducer 立即算出 State,这叫做同步;Action 发出以后,过一段时间再执行 Reducer,这就是异步。
怎么才能 Reducer 在异步操作结束后自动执行呢?这就要用到新的工具:中间件(middleware)。
一、中间件的概念
为了理解中间件,让我们站在框架作者的角度思考问题:如果要添加功能,你会在哪个环节添加?
(1)Reducer:纯函数,只承担计算 State 的功能,不合适承担其他功能,也承担不了,因为理论上,纯函数不能进行读写操作。
(2)View:与 State 一一对应,可以看作 State 的视觉层,也不合适承担其他功能。
(3)Action:存放数据的对象,即消息的载体,只能被别人操作,自己不能进行任何操作。
想来想去,只有发送 Action 的这个步骤,即store.dispatch()
方法,可以添加功能。举例来说,要添加日志功能,把 Action 和 State 打印出来,可以对store.dispatch
进行如下改造。
let next = store.dispatch; store.dispatch = function dispatchAndLog(action) {console.log('dispatching', action);next(action);console.log('next state', store.getState()); }
上面代码中,对store.dispatch
进行了重定义,在发送 Action 前后添加了打印功能。这就是中间件的雏形。
中间件就是一个函数,对store.dispatch
方法进行了改造,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能。
二、中间件的用法
本教程不涉及如何编写中间件,因为常用的中间件都有现成的,只要引用别人写好的模块即可。比如,上一节的日志中间件,就有现成的redux-logger模块。这里只介绍怎么使用中间件。
import { applyMiddleware, createStore } from 'redux'; import createLogger from 'redux-logger'; const logger = createLogger();const store = createStore(reducer,applyMiddleware(logger) );
上面代码中,redux-logger
提供一个生成器createLogger
,可以生成日志中间件logger
。然后,将它放在applyMiddleware
方法之中,传入createStore
方法,就完成了store.dispatch()
的功能增强。
这里有两点需要注意:
(1)createStore
方法可以接受整个应用的初始状态作为参数,那样的话,applyMiddleware
就是第三个参数了。
const store = createStore(reducer,initial_state,applyMiddleware(logger) );
(2)中间件的次序有讲究。
const store = createStore(reducer,applyMiddleware(thunk, promise, logger) );
上面代码中,applyMiddleware
方法的三个参数,就是三个中间件。有的中间件有次序要求,使用前要查一下文档。比如,logger
就一定要放在最后,否则输出结果会不正确。
三、applyMiddlewares()
看到这里,你可能会问,applyMiddlewares
这个方法到底是干什么的?
它是 Redux 的原生方法,作用是将所有中间件组成一个数组,依次执行。下面是它的源码。
export default function applyMiddleware(...middlewares) {return (createStore) => (reducer, preloadedState, enhancer) => {var store = createStore(reducer, preloadedState, 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}} }
上面代码中,所有中间件被放进了一个数组chain
,然后嵌套执行,最后执行store.dispatch
。可以看到,中间件内部(middlewareAPI
)可以拿到getState
和dispatch
这两个方法。
四、异步操作的基本思路
理解了中间件以后,就可以处理异步操作了。
同步操作只要发出一种 Action 即可,异步操作的差别是它要发出三种 Action。
- 操作发起时的 Action
- 操作成功时的 Action
- 操作失败时的 Action
以向服务器取出数据为例,三种 Action 可以有两种不同的写法。
// 写法一:名称相同,参数不同 { type: 'FETCH_POSTS' } { type: 'FETCH_POSTS', status: 'error', error: 'Oops' } { type: 'FETCH_POSTS', status: 'success', response: { ... } } // 写法二:名称不同 { type: 'FETCH_POSTS_REQUEST' } { type: 'FETCH_POSTS_FAILURE', error: 'Oops' } { type: 'FETCH_POSTS_SUCCESS', response: { ... } }
除了 Action 种类不同,异步操作的 State 也要进行改造,反映不同的操作状态。下面是 State 的一个例子。
let state = { // ... isFetching: true,didInvalidate: true,lastUpdated: 'xxxxxxx' };
上面代码中,State 的属性isFetching
表示是否在抓取数据。didInvalidate
表示数据是否过时,lastUpdated
表示上一次更新时间。
现在,整个异步操作的思路就很清楚了。
- 操作开始时,送出一个 Action,触发 State 更新为"正在操作"状态,View 重新渲染
- 操作结束后,再送出一个 Action,触发 State 更新为"操作结束"状态,View 再一次重新渲染
五、redux-thunk 中间件
异步操作至少要送出两个 Action:用户触发第一个 Action,这个跟同步操作一样,没有问题;如何才能在操作结束时,系统自动送出第二个 Action 呢?
奥妙就在 Action Creator 之中。
class AsyncApp extends Component {componentDidMount() {const { dispatch, selectedPost } = this.propsdispatch(fetchPosts(selectedPost))} // ...
上面代码是一个异步组件的例子。加载成功后(componentDidMount
方法),它送出了(dispatch
方法)一个 Action,向服务器要求数据 fetchPosts(selectedSubreddit)
。这里的fetchPosts
就是 Action Creator。
下面就是fetchPosts
的代码,关键之处就在里面。
const fetchPosts = postTitle => (dispatch, getState) => {dispatch(requestPosts(postTitle));return fetch(`/some/API/${postTitle}.json`).then(response => response.json()).then(json => dispatch(receivePosts(postTitle, json)));}; }; // 使用方法一 store.dispatch(fetchPosts('reactjs')); // 使用方法二 store.dispatch(fetchPosts('reactjs')).then(() =>console.log(store.getState()) );
上面代码中,fetchPosts
是一个Action Creator(动作生成器),返回一个函数。这个函数执行后,先发出一个Action(requestPosts(postTitle)
),然后进行异步操作。拿到结果后,先将结果转成 JSON 格式,然后再发出一个 Action( receivePosts(postTitle, json)
)。
上面代码中,有几个地方需要注意。
(1)
fetchPosts
返回了一个函数,而普通的 Action Creator 默认返回一个对象。(2)返回的函数的参数是
dispatch
和getState
这两个 Redux 方法,普通的 Action Creator 的参数是 Action 的内容。(3)在返回的函数之中,先发出一个 Action(
requestPosts(postTitle)
),表示操作开始。(4)异步操作结束之后,再发出一个 Action(
receivePosts(postTitle, json)
),表示操作结束。
这样的处理,就解决了自动发送第二个 Action 的问题。但是,又带来了一个新的问题,Action 是由store.dispatch
方法发送的。而store.dispatch
方法正常情况下,参数只能是对象,不能是函数。
这时,就要使用中间件redux-thunk
。
import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import reducer from './reducers'; // Note: this API requires redux@>=3.1.0 const store = createStore(reducer,applyMiddleware(thunk) );
上面代码使用redux-thunk
中间件,改造store.dispatch
,使得后者可以接受函数作为参数。
因此,异步操作的第一种解决方案就是,写出一个返回函数的 Action Creator,然后使用redux-thunk
中间件改造store.dispatch
。
六、redux-promise 中间件
既然 Action Creator 可以返回函数,当然也可以返回其他值。另一种异步操作的解决方案,就是让 Action Creator 返回一个 Promise 对象。
这就需要使用redux-promise
中间件。
import { createStore, applyMiddleware } from 'redux'; import promiseMiddleware from 'redux-promise'; import reducer from './reducers';const store = createStore(reducer,applyMiddleware(promiseMiddleware) );
这个中间件使得store.dispatch
方法可以接受 Promise 对象作为参数。这时,Action Creator 有两种写法。写法一,返回值是一个 Promise 对象。
const fetchPosts = (dispatch, postTitle) => new Promise(function (resolve, reject) {dispatch(requestPosts(postTitle));return fetch(`/some/API/${postTitle}.json`).then(response => {type: 'FETCH_POSTS',payload: response.json()}); });
写法二,Action 对象的payload
属性是一个 Promise 对象。这需要从redux-actions
模块引入createAction
方法,并且写法也要变成下面这样。
import { createAction } from 'redux-actions';class AsyncApp extends Component {componentDidMount() {const { dispatch, selectedPost } = this.props // 发出同步 Action dispatch(requestPosts(selectedPost)); // 发出异步 Action dispatch(createAction('FETCH_POSTS', fetch(`/some/API/${postTitle}.json`).then(response => response.json())));}
上面代码中,第二个dispatch
方法发出的是异步 Action,只有等到操作结束,这个 Action 才会实际发出。注意,createAction
的第二个参数必须是一个 Promise 对象。
看一下redux-promise
的源码,就会明白它内部是怎么操作的。
export default function promiseMiddleware({ dispatch }) {return next => action => {if (!isFSA(action)) {return isPromise(action)? action.then(dispatch): next(action);}return isPromise(action.payload)? action.payload.then(result => dispatch({ ...action, payload: result }),error => {dispatch({ ...action, payload: error, error: true });return Promise.reject(error);}): next(action);}; }
从上面代码可以看出,如果 Action 本身是一个 Promise,它 resolve 以后的值应该是一个 Action 对象,会被dispatch
方法送出(action.then(dispatch)
),但 reject 以后不会有任何动作;如果 Action 对象的payload
属性是一个 Promise 对象,那么无论 resolve 和 reject,dispatch
方法都会发出 Action。
中间件和异步操作,就介绍到这里。下一篇文章将是最后一部分,介绍如何使用react-redux
这个库。
(完)
转自:http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_two_async_operations.html
相关文章:

day09_读写分离_组件介绍
mysql中间件研究(Mysql-prxoy,Atlas,阿米巴,cobar,TDDL)mysql-proxyMySQL Proxy就是这么一个中间层代理,简单的说,MySQL Proxy就是一个连接池,负责将前台应用的连接请求转…

Idea其他设置
一、生成javadoc Tools->Gerenate JavaDoc 1. 选择是整个项目还是模块还是单个文件 2. 文档输出路径 3. Locale 选择地区,这个决定了文档的语言,中文就是zh_CN 4. 传入JavaDoc的参数,一般这样写 -encoding UTF-8 -charset UTF-8 -windowti…

freecodecamp_常见技术支持问题– freeCodeCamp常见问题解答
freecodecamp问题:我刚刚登录我的帐户,但看不到过去的任何进展。 (Question: I just signed into my account and I dont see any of my past progress.) Answer: You have created a duplicate account. Sign out of your account and try signing in u…

Redux 入门教程(三):React-Redux 的用法
前两篇教程介绍了 Redux 的基本用法和异步操作,今天是最后一部分,介绍如何在 React 项目中使用 Redux。 为了方便使用,Redux 的作者封装了一个 React 专用的库 React-Redux,本文主要介绍它。 这个库是可以选用的。实际项目中&…

SDN第二次上机作业
1、安装floodlight 2、生成拓扑并连接控制器floodlight,利用控制器floodlight查看图形拓扑 3、利用字符界面下发流表,使得‘h1’和‘h2’ ping 不通 4、利用字符界面下发流表,通过测试‘h1’和‘h3’的联通性,来验证openflow的har…

[KIWI syslog]Install document
安全日志的标准是rfc5425 它介绍SSL-tunnel日志标准和相关要求的标准定义。此外,IANA分配TCP端口6514作为一个标准的端口安全日志。 安装说明: 1.安装日志服务器 官方下载地址:http://www.kiwisyslog.com/products/. 安装步骤: 转…

机器学习数据拆分_解释了关键的机器学习概念-数据集拆分和随机森林
机器学习数据拆分数据集分割 (Dataset Splitting) Splitting up into Training, Cross Validation, and Test sets are common best practices. This allows you to tune various parameters of the algorithm without making judgements that specifically conform to trainin…

Oracle 导表异常处理方案 (解决空表导出出错问题)
Select alter table ||table_name|| allocate extent; from user_tables where num_rows0 or num_rows is null 然后执行查询语句 再导出数据 一个语句搞定: declare stmt varchar2(200); begin for tb in (select table_name from user_tables where seg…

10个你必须知道的ios框架
你好,iOS 开发者们!我的名字叫 Pawe?,我是一个独立 iOS 开发者,并且是 Enter Universe 的作者。 接近两年前我发布了iOS开源库,让你的开发坐上火箭吧。这是我在这里最棒的文章了(根据 Medium 用户的反馈来…

生成N个不相等的随机数
近期项目中须要生成N个不相等的随机数。实现的时候。赶工期,又有项目中N非常小(0-100)直接谢了一个最直观的方法: public static List<Integer> randomSet(int num,int threshold){Random random new Random();if(num > threshold) return null;Set<In…

kafka streams_如何使用Kafka Streams实施更改数据捕获
kafka streamsChange Data Capture (CDC) involves observing the changes happening in a database and making them available in a form that can be exploited by other systems. 更改数据捕获 (CDC)涉及观察数据库中发生的更改,并将其以可被其他系统利用的形式…

iOS超全开源框架、项目和学习资料汇总(1)UI篇
上下拉刷新控件**1. ** MJRefresh --仅需一行代码就可以为UITableView或者CollectionView加上下拉刷新或者上拉刷新功能。可以自定义上下拉刷新的文字说明。(推荐)**2. ** SVPullToRefresh --下拉刷新控件4500star,值得信赖**3. ** CBStoreHo…

day16 递归函数
一、递归 函数 为什么要有函数,提高代码的可读性,避免重复的代码,提高代码的复用性 在函数中能用return的不要print 1、递归的最大深度997 def foo(n):print(n)n1foo(n) foo(1) 递归的最大深度2、修改递归的最大深度 由此我们可以看出&#x…

设计模式之笔记--抽象工厂模式(Abstract Factory)
抽象工厂模式(Abstract Factory) 定义 抽象工厂模式(Abstract Factory),提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。 类图 描述 多个抽象产品类,每个抽象产品类可以派…

用户体验改善案例_如何检测用户的设备,以便改善他们的用户体验
用户体验改善案例A few months ago I watched a great talk from the Chrome Dev Summit about performance in slow devices.几个月前,我观看了Chrome开发者峰会上有关慢速设备性能的精彩演讲。 It blew my mind all the work done by Facebook in identifying de…

【如何快速的开发一个完整的iOS直播app】(采集篇)
前言在看这篇之前,如果您还不了解直播原理,请查看这篇文章如何快速的开发一个完整的iOS直播app(原理篇)开发一款直播app,首先需要采集主播的视频和音频,然后传入流媒体服务器,本篇主要讲解如何采集主播的视频和音频&am…

easyui 报表合并单元格
前段时间工作中碰到有需求,要求数据按下图所示格式来显示,当时在园子里看到了一篇文章(时间久了,想不起是哪一篇),研究了后做出了如下的DEMO,在此当作学习笔记,简单记录一下。 首先是…

HDU2594 KMP next数组的应用
这道题就是给你两个串s1, s2让你求出s1 s2的最长相同前缀和后缀, 我们直接将s1 s2连接到一起然后处理一下next数组即可, 注意答案应该是min(len(s1), len(s2) , next[len]), 代码如下: #include <cstdio> #include <cstring> #in…

c语言中浮点数和整数转换_C中的数据类型-整数,浮点数和空隙说明
c语言中浮点数和整数转换C中的数据类型 (Data Types in C) There are several different ways to store data in C, and they are all unique from each other. The types of data that information can be stored as are called data types. C is much less forgiving about d…

【如何快速的开发一个完整的iOS直播app】(美颜篇)
前言在看这篇之前,如果您还不了解直播原理,请查看这篇文章如何快速的开发一个完整的iOS直播app(原理篇)开发一款直播app,美颜功能是很重要的,如果没有美颜功能,可能分分钟钟掉粉千万,本篇主要讲解直播中美颜…

Linux内核分析——第五章 系统调用
第五章 系统调用 5.1 与内核通信 1、系统调用在用户空间进程和硬件设备之间添加了一个中间层,该层主要作用有三个: (1)为用户空间提供了一种硬件的抽象接口 (2)系统调用保证了系统的稳定和安全 (…

BZOJ 3110
http://www.lydsy.com/JudgeOnline/problem.php?id3110 整体二分区间修改树状数组维护 #include<cstdio> #define FOR(i,s,t) for(register int is;i<t;i) inline int max(int a,int b){return a>b?a:b;} inline int min(int a,int b){return a<b?a:b;} type…

css 选择器 伪元素_CSS伪元素-解释选择器之前和之后
css 选择器 伪元素选择器之前 (Before Selector) The CSS ::before selector can be used to insert content before the content of the selected element or elements. It is used by attaching ::before to the element it is to be used on.CSS ::before选择器可用于在选定…

各种面试题啊1
技术 基础 1.为什么说Objective-C是一门动态的语言? 什么叫动态静态 静态、动态是相对的,这里动态语言指的是不需要在编译时确定所有的东西,在运行时还可以动态的添加变量、方法和类 Objective-C 可以通过Runtime 这个运行时机制,…

PEP8 Python
写在前面 对于代码而言,相比于写,它更多是读的。 pep8 一、代码编排 缩进,4个空格的缩进,编辑器都可以完成此功能;每行最大长度79,换行可以使用反斜杠,换行点要在操作符的后边。类和top-level函…

粒子滤波 应用_如何使用NativeScript开发粒子物联网应用
粒子滤波 应用If youre developing any type of IoT product, inevitably youll need some type of mobile app. While there are easy ways, theyre not for production use.如果您要开发任何类型的物联网产品,则不可避免地需要某种类型的移动应用程序。 尽管有简单…

wkwebView基本使用方法
WKWebView有两个delegate,WKUIDelegate 和 WKNavigationDelegate。WKNavigationDelegate主要处理一些跳转、加载处理操作,WKUIDelegate主要处理JS脚本,确认框,警告框等。因此WKNavigationDelegate更加常用。 比较常用的方法: #p…

引用类型(一):Object类型
对象表示方式 1、第一种方式:使用new操作符后跟Object构造函数 var person new Object();<br/> person.name Nicholas;<br/> person.age 29; 2、对象字面量表示法 var person {name:Nicholas,age:29 } *:在age属性的值29的后面不能添加逗号…

(第四周)要开工了
忙碌的一周又过去了,这周的时间很紧,但是把时间分配的比较均匀,考研复习和各门功课都投入了一定的精力,所以不像前三周一样把大多数时间都花费在了软件工程上。也因为结对项目刚开始,我们刚刚进行任务分工以及查找资料…

统计数字,空白符,制表符_为什么您应该在HTML中使用制表符空间而不是多个非空白空间(nbsp)...
统计数字,空白符,制表符There are a number of ways to insert spaces in HTML. The easiest way is by simply adding spaces or multiple character entities before and after the target text. Of course, that isnt the DRYest method.有多种方法可以在HTML中插入空格。…