前言
经过上一篇文章(juejin.cn/post/684490…) 的学习,我们已经初步掌握了 Redux 的基本用法,并通过在原生 JavaScript 中的一个简单的例子加深了印象。但是还有个关键的问题没有解决:异步操作怎么办? Action 发出以后,Reducer 立即算出 State,这叫做同步;Action 发出以后,过一段时间再执行 Reducer,这就是异步。这就要用到新的工具:中间件(middleware)。
对于有服务端编程经验的开发者来说,对于 中间件(middleware)这个名词并不陌生。顾名思义,中间件作为中间设备、中间桥梁,它可以连接两种事务或服务。比如对于服务端来说,中间件经常被嵌入在从框架接收请求到产生相应的过程中。因此在这个环节,我们可以利用中间件完成扩展以适应不同的业务需求。
中间件的概念
Redux 提供的是位于 action 被派发之后,到达 reducer 之前的扩展点,因此我们可以利用 Redux 中间件来完成日志记录、调用异步接口或者路由等。
在 Redux 架构中可以接入多个中间件,每一个 middleware 都可以处理一个相对独立的业务需求且相互串联,派发给 redux store 的 action 对象,会被 store 上的多个中间件依次处理,middleware 在 Redux 的数据流中所处的位置如下图所示
了解了中间件的概念之后,我们来认识一些常用的中间件,并加以应用
如何接入中间件
Redux 本身提供了 applyMiddleware 方法来接入中间件,首先来看下创建 store 实例的代码:
const store = createStore(reducer, preloadedState, enhancer);
- reducer: 必传参数,为开发者编写的 reducer 函数
- preloadedState: 可选参数,页面的初始状态数据树
- enhancer: 可选参数,增强器
我们可以在 enhancer 参数的位置接入中间件,createStore 方法可以接收 applyMiddleware(...middlewares) 作为参数,创建一个应用了中间件之后的 store 。如下代码所示:
const store = createStore(
reducer,
preloadState,
applyMiddleware(middleware)
);
其中 preloadState 可以省略,也可以如下方式:
const store = createStore(
reducer,
applyMiddleware(middleware)
);
redux-logger 中间件
利用 redux-logger 中间件来进行日志记录。每一次派发前后的页面数据状态,以及当前所派发的 action,都能在浏览器开发面板中打印出来,方便开发者调试。
1. 引入 redux-logger 中间件
import createLogger from 'redux-logger';
2. 接入 redux-logger 中间件
import { applyMiddleware, createStore } from 'redux';
const logger = createLogger();
const store = {
reducer,
applyMiddleware(logger)
};
3. 在控制台查看
每次派发 action 后都可以在控制台看到改变前后的状态
redux-thunk 中间件
假如有一个异步需求,比如需要派发一个网络请求 action,在网络请求返回之后,再派发一个 action 用来根据返回的数据渲染页面。
如果先派发一个action,再派发一个处理请求返回结果的 action,这样显然是不行的,因为它们是同步进行的。设想一下:如果 dispatch 可以接受一个函数为参数,在函数体内进行异步操作,并在异步完成之后再派发相应的 action,那么便能解决问题了。这就是 redux-thunk 中间件所解决的问题。
1. 引入 redux-thunk 中间件
import thunk from 'redux-thunk';
const store = {
reducer,
applyMiddleware(thunk)
};
thunk 的使用
// 编写 reducer 函数
function reducer(state = initialState, action) {
switch(action.type) {
case 'GET_INFO_START':
return {info: '开始加载'}
case 'GET_INFO_SUCCESS':
return {info: action.data}
default:
return {info: '未加载'}
}
}
function getInfo() {
return dispatch => {
dispatch({type: 'GET_INFO_START', data: ''});
fetch('https://jsonplaceholder.typicode.com/posts/1').then((response) => {
dispatch({type: 'GET_INFO_SUCCESS', data: response.url});
})
}
}
// 调用
store.dispatch(getInfo())
不难发现,redux-thunk 对于异步处理的关键在于:使 dispatch 能够接收异步函数,之后变得灵活起来,我们可以控制 dispatch 相应 action 的时机。
redux-promise 中间件的使用
另一种异步操作的解决方案,就是让 dispatch 接收一个 Promise 对象,这就需要使用redux-promise中间件。
1. 引入 redux-promise 中间件
import promiseMiddleware from 'redux-promise';
const store = {
reducer,
applyMiddleware(promiseMiddleware)
};
2. redux-promise 的应用
// 编写 reducer 函数
function reducer(state = initialState, action) {
switch(action.type) {
case 'GET_INFO_START':
return {info: '开始加载'}
case 'GET_INFO_SUCCESS':
return {info: action.payload}
default:
return {info: '未加载'}
}
}
const fetchData = () => fetch('https://jsonplaceholder.typicode.com/posts/1');
async function getWeather() {
const result = await fetchData()
if (result.error) {
return {
type: 'GET_INFO_ERROR', payload: result.error,
}
}
return {
type: 'GET_INFO_SUCCESS', payload: result.url,
}
}
// 触发
store.dispatch(getWeather())
3. 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);
};
}