tags: 异步, Asynchronous, Action, Thunk, 回调
Redux: Asynchronous Action (异步Action)
处理异步的时候, 有两个非常关键的时刻:
- 发起请求的时刻
- 和接收到响应的时刻(也可能是超时)
处理异步请求的三种Action
为了处理异步, 每个API请求必须dispatch至少三种action:
-
通知 reducer 请求开始的 action
对于这种 action,reducer 可能会切换一下 state 中的 isFetching
标记。以此来告诉 UI 来显示加载界面。
-
通知 reducer 请求成功的 action
对于这种 action,reducer 可能会把接收到的新数据合并到 state 中,并重置 isFetching。UI 则会隐藏加载界面,并显示接收到的数据。
-
通知 reducer 请求失败的 action
对于这种 action,reducer 可能会重置 isFetching。另外,有些 reducer 会保存这些失败信息,并在 UI 里显示出来。
三种Action的实现
当然,这个只是推荐的实现办法, 最终还是要根据项目需求进行修改
为了区分这三种 action,可能在 action 里添加一个专门的 status
字段作为标记位:
1 2 3
| { type: 'FETCH_POSTS' } { type: 'FETCH_POSTS', status: 'error', error: 'Oops' } { type: 'FETCH_POSTS', status: 'success', response: { ... } }
|
又或者为它们定义不同的 type:
1 2 3
| { type: 'FETCH_POSTS_REQUEST' } { type: 'FETCH_POSTS_FAILURE', error: 'Oops' } { type: 'FETCH_POSTS_SUCCESS', response: { ... } }
|
将Action和网络请求链接起来
这里通过redux-thunk
实现, 当然还有很多种其他的实现办法
首先要了解一下Thunk
Thunk?
Thunk是一个类似于外挂
的middleware
可以在某些请求之上进行一些外的处理
如何创建一个能被Thunk处理的Action
引用这个库: redux-thunk
- 实现方法: 当action 创建函数返回函数时,这个函数会被 Redux Thunk middleware执行。
- 这个函数并不需要保持纯净;它还可以带有副作用,包括执行异步 API 请求。这个函数还可以 dispatch action,就像 dispatch 前面定义的同步 action 一样。
- 甚至这个Action可以返回对另一个Action的Dispatch, 类似于回调的样子
所以很简单, 让action返回一个函数就行, 这个函数里面可以进行各种乱七八糟的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
export function fetchPosts(subreddit) {
return function (dispatch) {
dispatch(requestPosts(subreddit))
return fetch(`http://www.subreddit.com/r/${subreddit}.json`) .then( response => response.json(), error => console.log('An error occurred.', error) ) .then(json =>
dispatch(receivePosts(subreddit, json)) ) } }
|
详细实现
createStore() init的时候多传一个参数
默认情况下,createStore()
所创建的 Redux store 没有使用 middleware
我们多传一个参数,将中间件一起传进去:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import thunk from 'redux-thunk' import { createLogger } from 'redux-logger' import reducer from './reducers'
const middleware = [ thunk ] if (process.env.NODE_ENV !== 'production') { middleware.push(createLogger()) }
const store = createStore( reducer, applyMiddleware(...middleware) )
|
另一个例子:
1 2 3 4 5 6 7
| const store = createStore( rootReducer, applyMiddleware( thunkMiddleware, // lets us dispatch() functions loggerMiddleware // neat middleware that logs actions ) )
|
dispatch部分
和普通Action一样, dispatch一个Action:
1 2 3 4
| componentDidMount() { const { dispatch, selectedSubreddit } = this.props dispatch(fetchPostsIfNeeded(selectedSubreddit)) }
|
比如这个地方的Action是fetchPostsIfNeeded()
如果是普通的Action, 它应该返回一个Obj作为state结果
但是这里我们要通过Thunk进行处理, 因此返回一个函数
返回的函数里面还可以返回另一个dispatch, 这个dispatch又可以返回另一个函数, 最终只要再返回函数了, 那么就结束回调
action: fetchPosts
里面返回另一个函数, 这个函数里面执行了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| /* 0. 首先componentDidMount()里面dispatch第一个action: fetchPostsIfNeeded */ /* 1. fetchPostsIfNeeded 里面返回一个函数, 这个函数返回另一个dispatch, 这个dispatch会dispatch另一个action: fetchPost */ export const fetchPostsIfNeeded = subreddit => (dispatch) => {
...(some validation) return dispatch(fetchPosts(subreddit)) }
/* 2. 同样也会返回一个函数,这个函数里面会处理一次dispatch(并不是返回), 返回进行http请求然后返回另一个Promise, Primise的最终回调会提交action: receivePosts */ const fetchPosts = subreddit => dispatch => { dispatch(requestPosts(subreddit)); return fetch(`https://www.reddit.com/r/${subreddit}.json`) .then(response => response.json()) .then(json => dispatch(receivePosts(subreddit, json))) }
// 3. receivePosts就是简单的state处理了 const receivePosts = (subreddit, json) => ({ type: RECEIVE_POSTS, subreddit, posts: json.data.children.map(child => child.data), receivedAt: Date.now() })
|
Middleware
可以明显的看到实现Asynchronous Action是基于Middleware实现的
当然Middleware可以自己写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
const logger = store => next => action => { console.log('dispatching', action) let result = next(action) console.log('next state', store.getState()) return result }
const crashReporter = store => next => action => { try { return next(action) } catch (err) { console.error('Caught an exception!', err) Raven.captureException(err, { extra: { action, state: store.getState() } }) throw err } }
|
这堆代码看起来很见鬼, 特别是链式调用
换成named function
的形式可能会好理解一些(但是当然不推荐用下面这个老方法):
1 2 3 4 5 6 7 8 9 10 11 12 13
| function logger(store) { return function wrapDispatchToAddLogging(next) { return function dispatchAndLog(action) { console.log('dispatching', action) let result = next(action) console.log('next state', store.getState()) return result } } }
|
然后是将它们引用到 Redux store 中:
1 2 3 4 5 6 7 8
| import { createStore, combineReducers, applyMiddleware } from 'redux'
let todoApp = combineReducers(reducers) let store = createStore( todoApp, applyMiddleware(logger, crashReporter) )
|
再然后任何发送到 store 的 action 都会经过 logger
和 crashReporter
:
1 2 3
| // 将经过 logger 和 crashReporter 两个 middleware! store.dispatch(addTodo('Use Redux'))
|