[React] 12 - Redux: async & middleware
这里只是简单地了解中间件的概念,对于异步,貌似之后要讲的saga更胜一筹。
reducer计算新状态的策略:
- Action 发出以后,Reducer 立即算出 State,这叫做同步;
- Action 发出以后,过一段时间再执行 Reducer,这就是异步。
何为中间件
一、安插中间件的位置

middleware提供的是位于 action 被发起之后,到达 reducer 之前的扩展点。
- 原程序
const Counter = ({ value, onIncrement, onDecrement }) => (
<div>
<h1>{value}</h1>
<button onClick={onIncrement}>+</button>
<button onClick={onDecrement}>-</button>
</div>
);
...............................................................
/**
* 更新过程:
* action + old state ==> new state
* 也可以考虑combineReducers的形式
*/
const reducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT': return state + 1; // step 2: how to implement this action --> automatically trigger step 3: UI
case 'DECREMENT': return state - 1;
default: return state;
}
};
...............................................................
const store = createStore(reducer);
/**
* 渲染过程:
* 既接收动作,也处理界面更新
* 当然,具体的更新html还要归于具体的html代码,也就是最上面的Counter组件的定义
*/
const render = () => {
ReactDOM.render(
<Counter
value={store.getState()} // step 3: render new ui based on new state
onIncrement={() =>store.dispatch({type: 'INCREMENT'})} // step 1: receive action --> automatically trigger step 2: reducer
onDecrement={() => store.dispatch({type: 'DECREMENT'})}
/>,
document.getElementById('root')
);
};
render();
store.subscribe(render);
- 功能增强
对store.dispatch做了新的定义:
--- 不仅给store发送信号action。
--- 而且附带了log的功能。
let next = store.dispatch;
store.dispatch = function dispatchAndLog(action) {
console.log('dispatching', action);
next(action);
console.log('next state', store.getState());
}
再理解如下文字:
(1)Reducer:纯函数,只承担计算 State 的功能,不合适承担其他功能,也承担不了,因为理论上,纯函数不能进行读写操作。
(2)View:与 State 一一对应,可以看作 State 的视觉层,也不合适承担其他功能。
(3)Action:消息的载体,让reducer的纯函数去操作,自己不用干活er。
二、中间件的用法
作为createStore的参数来注册。
applyMiddlewares(...),Redux 的原生方法,作用是将所有中间件组成一个数组,依次执行。
const store = createStore(
reducer,
initial_state, // 有第二参数则表示整个程序的初始状态
applyMiddleware(logger)
);
const store = createStore(
reducer,
applyMiddleware(thunk, promise, logger) // 这里的log一定要放在最后
);
三、实例分析
通过中间件,增强Log的用法。
- 在reducer之前执行
store.dispatch ==> middleware --> logger ==> "action fired."

- 添加 next(action)
其实就是一个多层嵌套返回函数的函数,
柯里化 - 使用箭头的写法在函数式编程,对柯里化更详细的介绍可以看一看这篇 张鑫旭的博客。
第一个(最外层)方法的参数是一个包含dispatch和getState字段(方法)的对象,其实就是store对象,所以也可以写成:
# 一个Redux中间件的基本写法
store => next => action => {
...
};
参数next是一个方法,作用是:通知下一个Redux中间件对这次的action进行处理。
next(action),如果一个中间件中没有执行next(action),则action会停止向后续的中间件传递,并阻止reducer的执行(store将不会因为本次的action而更新)。
import { applyMiddleware, createStore } from "redux";
const reducer = (initialState=0, action) => {
if (action.type === "INC") {
return initialState + 1;
} else if (action.type === "DEC") {
return initialState - 1;
} else if (action.type === "MULT") {
throw new Error("AHHHH!!");
}
return initialState;
}
-------------------------------------------------------------
const logger = (store) => (next) => (action) => {
console.log("Logged", action);
return next(action);
};
const errorHandler = (store) => (next) => (action) => {
try {
return next(action);
} catch(e) {
console.log("ERROR!", e);
}
};
const middleware= applyMiddleware(
logger,
errorHandler
)
-------------------------------------------------------------
const store = createStore(reducer, middleware)
store.subscribe(() => {
console.log("store changed", store.getState());
})
store.dispatch({type: "INC"})
store.dispatch({type: "INC"})
store.dispatch({type: "INC"})
store.dispatch({type: "DEC"})
store.dispatch({type: "DEC"})
store.dispatch({type: "DEC"})
store.dispatch({type: "MULT"})
store.dispatch({type: "DEC"})
- redux-logger 使用

异步操作实现
一、用户送出第一个 Action
Ref: RUAN的博文可能更容易理解些
如果发送的信号(action)涉及到服务端,那么异步就是不可避免的事情。
- 整个异步操作的思路:
第一个action:操作开始时,送出一个 Action,触发 State 更新为"正在操作"状态,View 重新渲染
第二个action:操作结束后,再送出一个 Action,触发 State 更新为"操作结束"状态,View 再一次重新渲
- 异步需要三种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: { ... } }
- State 也要进行改造,反映不同的操作状态:
let state = {
// ...
isFetching: true, // 表示是否在抓取数据
didInvalidate: true, // 表示数据是否过时
lastUpdated: 'xxxxxxx' // 表示上一次更新时间
};
二、自动送出第二个 Action
- 代码背景
异步操作至少要送出两个 Action:
- 用户触发第一个 Action,这个跟同步操作一样,没有问题;
- 如何才能在操作结束时,系统自动送出第二个 Action 呢?
# 异步组件的例子
class AsyncApp extends Component {
componentDidMount() {
const { dispatch, selectedPost } = this.props
dispatch(fetchPosts(selectedPost)) // fetchPosts是送给server的信号(action)
}
// ...
- Step 1 - 返回的是一个函数,而非”对象”
What kind of ActionCreator it is?
(1) 先发出一个Action(requestPosts(postTitle)) ----> 然后进行异步操作。
(2) 拿到结果后,先将结果转成 JSON 格式 ----> 然后再发出一个 Action( receivePosts(postTitle, json))。
const fetchPosts = postTitle => (dispatch, getState) => {
dispatch(requestPosts(postTitle));
return fetch(`/some/API/${postTitle}.json`)
.then(response => response.json())
.then(json => dispatch(receivePosts(postTitle, json)));
};
};
- Step 2 - 如何使用
// 使用方法一
store.dispatch( fetchPosts('reactjs') );
// 使用方法二
store.dispatch( fetchPosts('reactjs') ).then(() =>
console.log(store.getState())
);
返回的函数的参数:dispatch和getState这两个 Redux 方法,而非 Action 的内容。
- 注意事项
(1)fetchPosts 返回了一个函数,而普通的 Action Creator 默认返回一个对象。
(2)返回的函数的参数是dispatch和getState这两个 Redux 方法,普通的 Action Creator 的参数是 Action 的内容。
(3)在返回的函数之中,先发出一个 Action(requestPosts(postTitle)),表示操作开始。
(4)异步操作结束之后,再发出一个 Action(receivePosts(postTitle, json)),表示操作结束。
三、中间件帮助"参数扩展"
- redux-thunk:方案一,使参数支持 “函数"
通过中间件redux-thunk,改造store.dispatch,使得后者可以接受函数作为参数。
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( // 其实就是返回了一个特殊的store,是store.dispatch支持函数作为参数了
reducer,
applyMiddleware(thunk)
);
- redux-promise:方案二,使参数支持 “Promise对象”
让 Action Creator 返回一个 Promise 对象,乃另一种异步操作的解决方案 through 使用redux-promise中间件。
import { createStore, applyMiddleware } from 'redux';
import promiseMiddleware from 'redux-promise';
import reducer from './reducers';
const store = createStore(
reducer,
applyMiddleware(promiseMiddleware)
);
详情请见:[JS] ECMAScript 6 - Async : compare with c#
所谓
Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
接受 Promise 对象作为参数,有两种写法:
** 写法一,返回值是一个 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 对象。
import { createAction } from 'redux-actions';
class AsyncApp extends Component {
componentDidMount() {
const { dispatch, selectedPost } = this.props
// 发出同步 Action
dispatch(requestPosts(selectedPost));
// 发出异步 Action, 只有等到操作结束,这个 Action 才会实际发出;
// 注意,createAction的第二个参数必须是一个 Promise 对象。
dispatch(createAction(
'FETCH_POSTS', // 第一个参数
fetch(`/some/API/${postTitle}.json`) // 第二个参数
.then(response => response.json())
));
}
代码举例子:payload属性是一个 Promise 对象
----> Without promise, 发送完信号后,还要听过axios.then...catch...定义”返回状态处理“函数。

----> With promise, 如下使用axios (基于promise的http库)。
import { applyMiddleware, createStore } from "redux";
import axios from "axios";
import logger from "redux-logger";
import thunk from "redux-thunk";
import promise from "redux-promise-middleware";
const initialState = {
fetching: false,
fetched: false,
users: [],
error: null,
};
// 改变state的部分value
const reducer = (state=initialState, action) => {
switch (action.type) {
/**
* 因为使用了promise,所以默认使用promise的性质
* 其中包括了promise的三个状态定义:pending, rejected, fulfilled
*/
case "FETCH_USERS_PENDING": {
return {...state, fetching: true}
break;
}
case "FETCH_USERS_REJECTED": {
return {...state, fetching: false, error: action.payload}
break;
}
case "FETCH_USERS_FULFILLED": {
return {
...state,
fetching: false,
fetched: true,
users: action.payload,
}
break;
}
}
return state
}
const middleware = applyMiddleware(promise(), thunk, logger())
const store = createStore(reducer, middleware)
// 因为promise,这里就省去了
store.dispatch({
type: "FETCH_USERS", # 自定发送”添加promose默认后缀“后的信号(action)
payload: axios.get("http://rest.learncode.academy/api/wstern/users")
})
[React] 12 - Redux: async & middleware的更多相关文章
- [React] 14 - Redux: Redux Saga
Ref: Build Real App with React #14: Redux Saga Ref: 聊一聊 redux 异步流之 redux-saga [入门] Ref: 从redux-thun ...
- react脚手架改造(react/react-router/redux/eslint/karam/immutable/es6/webpack/Redux DevTools)
公司突然组织需要重新搭建一个基于node的论坛系统,前端采用react,上网找了一些脚手架,或多或少不能满足自己的需求,最终在基于YeoMan的react脚手架generator-react-webp ...
- 深入理解React、Redux
深入理解React.ReduReact+Redux非常精炼,良好运用将发挥出极强劲的生产力.但最大的挑战来自于函数式编程(FP)范式.在工程化过程中,架构(顶层)设计将是一个巨大的挑战.要不然做出来的 ...
- 基于react+react-router+redux+socket.io+koa开发一个聊天室
最近练手开发了一个项目,是一个聊天室应用.项目虽不大,但是使用到了react, react-router, redux, socket.io,后端开发使用了koa,算是一个比较综合性的案例,很多概念和 ...
- [React] 15 - Redux: practice IM
本篇属于私人笔记. client 引导部分 一.assets: 音频,图片,字体 ├── assets │ ├── audios │ ├── fonts │ └── images 二.main&quo ...
- React 与 Redux 在生产环境中的实践总结
React 与 Redux 在生产环境中的实践总结 前段时间使用 React 与 Redux 重构了我们360netlab 的 开放数据平台.现将其中一些技术实践经验总结如下: Universal 渲 ...
- immutable.js 在React、Redux中的实践以及常用API简介
immutable.js 在React.Redux中的实践以及常用API简介 学习下 这个immutable Data 是什么鬼,有什么优点,好处等等 mark : https://yq.aliyu ...
- react native redux 草稿
Provider > Provider > 使组件层级中的 方法都能够获得 Redux store.正常情况下,你的根组件应该嵌套在 Provider 中才能使用 方法. 如果你真的不想把 ...
- 实例讲解react+react-router+redux
前言 总括: 本文采用react+redux+react-router+less+es6+webpack,以实现一个简易备忘录(todolist)为例尽可能全面的讲述使用react全家桶实现一个完整应 ...
随机推荐
- centos6.9 忘记密码解决方法
若果忘记了 root 的密码,解决方法如下: 我采用的 linux 版本是 centos-6.9 , 经过亲身实践证明,该方法是 ok 的 在开机启动的时候按键盘上的“E”键会进入如下界面. 选择相应 ...
- phantomjs + python 打造一个微信机器人
phantomjs + python 打造一个微信机器人 1.前奏 媳妇公司不能上网,但经常需要在公众号上找一些文章做一些参考,需要的时候就把文章链接分享给我,然后我在浏览器打开网页,一点点复制过 ...
- windows命名管道
命名管道是通过网络来完成进程间的通信,它屏蔽了底层的网络协议细节. 将命名管道作为一种网络编程方案时,它实际上建立了一个C/S通信体系,并在其中可靠的传输数据.命名管道服务器和客户机的区别在于:服务器 ...
- Hadoop3集群搭建之——hbase安装及简单操作
折腾了这么久,hbase终于装好了 ------------------------- 上篇: Hadoop3集群搭建之——虚拟机安装 Hadoop3集群搭建之——安装hadoop,配置环境 Hado ...
- Python:提取网页中的电子邮箱
import requests, re #regex = r"([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+)"#这个正则表达式过滤 ...
- js显示屏幕分辨率
<div style=" width:88%;margin:30px auto; color:blue;" id="div_html"> </ ...
- Win10更新搜狗输入法后重启输入密码蓝屏
解决办法:如果能进入安全模式,卸载搜狗输入法:不行的话(好像不行)只能重装系统:因为蓝屏后就基本开不了了!!!生气!! win10 1809 19.3月累积更新之后蓝屏:安装了搜狗输入法的win10 ...
- PL/SQL学习笔记之数据类型中的标量、LOB
一:标量 标量 即 基本数据类型,主要有4种:数值.字符.布尔类型.日期时间. 1:数值类型 数据类型 描述 PLS_INTEGER 通过2,147,483,647到-2147483648范围内有符号 ...
- 关于FMDatabase executeQuery的问题
如果你碰到这个问题,请查看idx的值,并查看SQL语句中第6个字段的值有问题(从0开始),比如你给的值是NSInteger会报错,需要将其转成NSString.
- SoapUI Pro Project Solution Collection –Easy develop Groovy Script to improve SoapUI ability
As you know the groovy script and java script language is the soapui supported .but unfortunately So ...