十、中间件与异步操作
Redux
的基本做法:用户发出 Action
, Reducer
函数算出新的 State
, View
重新渲染
- 一个关键问题没有解决:异步操作怎么办?
Action
发出以后,Reducer
立即算出State
,这叫做同步;Action
发出以后,过一段时间再执行Reducer
,这就是异步 - 怎么才能
Reducer
在异步操作结束后自动执行呢?这就要用到新的工具:中间件(middleware
)
10.1 中间件的概念
中间件就是一个函数,对 store.dispatch
方法进行了改造,在发出 Action
和执行 Reducer
这两步之间,添加了其他功能。
10.2 中间件的用法
常用的中间件都有现成的,只要引用别人写好的模块即可。比如日志中间件,就有现成的 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
就一定要放在最后,否则输出结果会不正确
10.3、applyMiddlewares()
applyMiddlewares
这个方法。它是 Redux
的原生方法,作用是将所有中间件组成一个数组,依次执行
10.4 异步操作的基本思路
理解了中间件以后,就可以处理异步操作了
- 同步操作只要发出一种
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
再一次重新渲染
10.5 redux-thunk 中间件
异步操作至少要送出两个 Action
:用户触发第一个 Action
,这个跟同步操作一样,没有问题;如何才能在操作结束时,系统自动送出第二个 Action
呢
- 奥妙就在
Action Creator
之中
class AsyncApp extends Component { componentDidMount() { const { dispatch, selectedPost } = this.props dispatch(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)
)
上面代码中,有几个地方需要注意
fetchPosts
返回了一个函数,而普通的Action Creator
默认返回一个对象- 返回的函数的参数是
dispatch
和getState
这两个Redux
方法,普通的Action Creator
的参数是Action
的内容 - 在返回的函数之中,先发出一个
Action(requestPosts(postTitle))
,表示操作开始 - 异步操作结束之后,再发出一个
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
10.6、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
对象
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论