异步操作图 4 显示了图书搜索应用程序的下一个版本。
图 4. 异步搜索图书您已从 中知道,图 4 中所示的图书缩略图来自 Google Books API。通过直接在浏览器中查询 URL googleapis.com(比如键入 http://www.googleapis.com/v11/volumes/?q=javascript 作为网址),您可以获得 JSON 格式的查询结果,如图 5 所示。
图 5. 使用 Google Books REST API 搜索图书 应用程序的所有操作目前都是简单的对象。对于异步操作,我不想要对象,而是想要一个在异步请求处理过程中分派操作的函数。
清单 10 显示了图书搜索应用程序的更新后的入口点。
清单 10. 在入口点抓取图书信息 (index.js)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './containers/app';
import store from './store';
import { setTopic, setDisplayMode, fetchBooks } from './actions';
store.dispatch(setTopic('javascript'));
store.dispatch(setDisplayMode('THUMBNAIL'));
store.dispatch(fetchBooks());
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('example')
)
|
清单 10 与该应用程序的入口点的之前版本的唯一区别在于抓取图书操作的分派。该操作的创建者是 fetchBooks() 函数,如清单 11 所示。
清单 11. 异步操作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
41
| /**
* The fetchBooks action creator returns a function instead of an object.
* Custom middleware is necessary for that to work.
*/
const fetchBooks = () => {
return fetchCurrentTopic;
}
const fetchStart = () => {
return {
type: 'FETCH_STARTED'
}
}
const fetchComplete = json => {
return {
type: 'FETCH_COMPLETE',
json
}
}
const fetchFailed = error => {
return {
type: 'FETCH_FAILED',
error
}
}
const setTopic = topic => {
return {
type: 'SET_TOPIC',
topic
}
}
const setDisplayMode = displayMode => {
return {
type: 'SET_DISPLAY_MODE',
displayMode
}
}
|
清单 11 实现了 6 个操作创建器。回想一下 ,操作创建器是返回操作的函数。
setTopic() 和 setDisplayMode() 操作创建器与之前的实现相同。除了 setTopic() 和 setDisplayMode() 之外,清单 11 中实现的 6 个操作创建器中的另外 3 个也将返回操作对象。这些操作创建器返回的操作会在发生异步抓取时指定状态:
- fetchStart()
- fetchComplete()
- fetchFailed()
第 6 个抓取操作创建器 (fetchBooks()) 返回一个函数而不是对象。该函数(如清单 12 所示)使用 JavaScript fetch API 来抓取图书,但(更重要的是)请注意,它会在合适的时间点分派其他抓取操作。
清单 12. 抓取当前主题1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| const URL = 'https://www.googleapis.com/books/v1/volumes?q=';
const fetchCurrentTopic = (dispatch, state) => {
dispatch(fetchStart())
fetch(URL + state.topic)
.then(res => {
return res.json()
})
.then(json => {
if(json.error) {
dispatch(fetchFailed(json.error))
}
else {
dispatch(fetchComplete(json))
}
})
.catch(error => {
dispatch(fetchFailed(json.error))
})
}
|
像所有 Redux 操作一样,抓取操作由缩减程序处理(缩减)。该应用程序将该缩减程序与显示模式和主题缩减程序组合在一起。
清单 13 显示了 reducers.js 的部分代码,展示了抓取缩减程序如何处理抓取操作。
清单 13. 抓取缩减程序(reducers.js — 部分代码)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
| import { combineReducers } from 'redux';
const defaults = {
TOPIC: 'javascript',
DISPLAY_MODE: 'THUMBNAIL',
BOOKS: []
}
...
const fetchReducer = (state = defaults.BOOKS, action) => {
switch(action.type) {
case 'FETCH_STARTED':
case 'FETCH_FAILED':
return [];
case 'FETCH_COMPLETE':
return action.json.items
default:
return state;
}
}
// Combine reducers
export default combineReducers({
topic: topicReducer,
displayMode: displayModeReducer,
books: fetchReducer
});
|
当抓取开始或失败时,抓取缩减程序会返回一个空数组。当抓取完成时,抓取缩减程序返回从 REST 调用返回的项。
回顾一下我刚才的操作:
我实现了一个抓取操作创建器,它返回一个函数,而不是像大部分操作创建器一样返回一个对象。该函数使用 Google Books API 异步抓取图书,并在异步抓取的各个阶段,通过使用操作创建器创建操作并使用 Redux dispatch() 函数分派操作来分派其他操作。
抓取操作由抓取缩减程序处理。当抓取开始时,抓取缩减程序返回的状态是一个空数组。当抓取成功时,抓取缩减程序返回抓取的图书。如果抓取失败,抓取缩减程序会将状态重置为空数组。
但是,这里存在一个问题。默认情况下,Redux 仅支持对象形式的操作,不支持函数形式的操作。如果您尝试运行本节中的代码,则会看到一个错误:
Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
|
因为 Redux 不支持函数形式的操作,所以我需要指导它支持这类操作 — 通过实现 Redux 中间件。 |