asynchronous action creator 做数据取回,至少发送3个不同的actions:
- an action 查询store, 请求开始(request began)
- an action 查询store, 请求成功的完成(request finished successfully)
- an action 查询store, 请求失败(request failed)
下面以pro react中的示例作介绍,这个项目主要是查询飞机航班信息和飞机票信息了解具体操作流程:
1.目录结构
-
app/
- actions/
- api/
- components/
- reducers/
- store/
- index.js
- constants.js
其中 粗体带/
表示文件夹, 斜体
表示文件, app/ 文件夹在根目录下。
2.定义constants.js
这个常量文件主要是项目中发送请求的类型,通常都是一边开发,一边根据需求来实现的,这里由于是示例,暂且先一次性写出来:
// constants.js
/*
* REQUEST_AIRPORTS: 表示应用开始取回航班信息
* RECEIVE_AIRPORTS: 通过负载(preloaders)来表示异步取回信息失败或者成功(注意这个动作表示2种状态)
* CHOOSE_AIRPORT : 选择起点和终点的操作,同步操作
* REQUEST_TICKETS : 表示应用请求票务信息
* RECEIVE_TICKETS : 异步取回票务信息成功或失败(注意这个动作表示2种状态,成功或失败)
*/
export default const REQUEST_AIRPORTS = 'request airports';
export default const RECEIVE_AIRPORTS = 'receive airports';
export default const CHOOSE_AIRPORT = 'choose airport';
export default const REQUEST_TICKETS = 'request tickets';
export default const RECEIVE_TICKETS = 'receive tickets';
3.创建辅助API函数,用来获取服务器中的数据
这些数据APIs现在都由示例提供,通过上面的示例我们知道有2次异步的操作: 取回航班信息和取回票务信息。
本例使用 fetch
进行ajax操作,返回一个promise,在 api/文件夹下:
// api/AirCheapAPI.js
import 'whatwg-fetch';
// 用fetch取回数据
// 对数据进行json()格式化
// 整个函数返回一个promise, 等待进一步操作
const AirCheapAPI = {
// 取回航班信息
fetchAirports() {
return fetch('https://aircheapapi.pro-react.com/airports')
.then(response => response.json());
},
// 取回票务信息
fetchTickets(origin, destination) {
return fetch(`https://aircheapapi.pro-react.com/tickets?origin=${origin}&destination=${destination}`)
.then(response => response.json());
}
}
export default AirCheapAPI;
4.AirportActionCreators
根据常量中的动作类型和需求来定义actions, 再将其写成函数的形式,方便传入到数据中,在实际的开发中,这些不可能一蹴而就,往往都是按需开发。
// actions/AirportActionCreators.js
import {
REQUEST_AIRPORTS,
RECEIVE_AIRPORTS,
CHOOSE_AIRPORT,
REQUEST_TICKETS,
RECEIVE_TICKETS
} from '../constants';
import AirCheapAPI from '../api/AirCheapAPI';
const AirportActionCreators = {
// 获取航班信息
// 异步操作, 返回一个函数
// Thunk action creator
fetchAirportsInfo(origin, destination) {
return (dispatch) => {
// 本地发送请求航班信息
dispatch({ type: REQUEST_AIRPORTS });
// promise
// 取回数据成功(success), 则发送RECEIVE_AIRPORTS动作和preload
// success, airports为负载
// 取回数据失败(error), 则发送RECEIVE_AIRPORTS动作和preload
AirCheapAPI.fetchAirports().then(
(success) => dispatch({ type: RECEIVE_AIRPORTS, success: true, airports }),
(error) => dispatch({ type: RECEIVE_AIRPORTS, success: false })
)
};
},
// 选择出发地和目的地
// 同步操作, 返回一个action 对象
// target, code 都是发送动作时的负载
chooseAirport(target, airport) {
return {
type: CHOOSE_AIRPORT,
target,
code: airport ? airport.value : ''
}
},
// 获取票务信息
// 异步操作, 返回一个函数
fetchTicketsInfo(origin, destination) {
return (dispatch) => {
dispatch({ type: REQUEST_TICKETS });
AirCheapAPI.fetchTickets(origin, destination).then(
(success) => dispatch({ type: RECEIVE_TICKETS, success: true, tickets}),
(error) => dispatch({ type: RECEIVE_TICKETS, success: false })
)
};
}
}
export default AirportActionCreators;
通过上面可以看出,异步操作一般调用返回一个函数,调用辅助api,而同步操作,返回一个action对象即可
4.reducers
写完actionCreators之后就该写reducers(通常需要知道数据结构),一般将大的reducers工具功能划分成小的单个reducer,最后再合并成一个rootReducer。
更具constants.js可以知道总共有5种类型的action type, 但其中 REQUEST_AIRPORTS
和REQUEST_TICKETS
在异步操作中完成。
airports.js:
// reducers/airports.js
import { RECEVIE_AIRPORTS } from '../constants';
const airports = (state = [], action) => {
switch (action.type) {
case RECEVIE_AIRPORTS:
return action.tickets;
default:
return state;
}
}
export default airports;
route.js:
// reducers/route.js
import update from 'react-addons-update';
import { CHOOSE_AIRPORT } from '../constants';
const initialState = {
origin: '',
destination: ''
};
const route = (state = initialState, action) => {
switch (action.type) {
case CHOOSE_AIRPORT:
// 更新状态树中的route状态
// action.target 表示 'origin'或者'destination'
return update(state, { [action.target]: { $set: action.code } });
default:
return state;
}
}
export default route;
tickets.js:
// reducers/tickets.js
import { REQUEST_TICKETS, RECEVIE_TICKETS } from '../constants';
const tickets = (state = [], action) => {
switch (action.type) {
// 这里的REQUEST_TICKETS主要用于清空状态树中tickets的内容
case REQUEST_TICKETS:
return [];
case RECEVIE_TICKETS:
return action.tickets;
default:
return state;
}
}
export default tickets;
将多个reducers合并成一个rootReducer, index.js:
// reducers/index.js
import { combineReducers } from 'redux';
import airports from './airports';
import route from './route';
import tickets from './tickets';
const rootReducer = combineReducers({
airports,
route,
tickets
});
export default rootReducer;
5.store
完成reducers之后,下一步就是写store了,因为异步的原因,需要添加中间件react-thunk,另外这里自己写一个logger的中间件。
// store/index.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'react-thunk';
import rootReducer from '../reducers/index';
// 自定义logger中间件
const logger = (store) => (next) => (action) => {
if (typeof action !== 'function') {
console.log('dispatching:', action)
}
next(action);
};
const aircheapStore = createStore(
rootReducer,
applyMiddleware(logger, thunk)
);
export default aircheapStore;
如果配合redux-devtools chrome插件,可以写为:
import { createStore, applyMiddleware, compose } from 'redux';
// ...
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const aircheapStore = createStore(
rootReducer,
composeEnhancers(applyMiddleware(logger, thunk))
);
export default aircheapStore;
到目前为止,所有的基本框架已经搭建好了,剩下的就是去写components/中的组件了,当然这只是示例,实际开发中肯定要先写组件,然后依据需求添加上面的api, constants, actions, reducers, store。
总结
写这个主要是为了熟悉开发流程,以及异步操作的处理当中要注意的一些事项和规范。真实项目目前做的比较少,大致流程可以理解为:
- 写设计组件结构,由几部分组件,写出presentation components(视觉组件)
- 然后初步列举出所需要的功能,将功能的实现以动作的形式命名,写入到constants.js中
- 列举出可能会出项的异步操作,写入到辅助api中
- 根据动作类型写出actionCreator, 及对应的reducer
- 将routeReducer写入store
- 再写container components(容器组件), 然后调用store中的所需要的状态
当然状态树(state tree)这一点上对于我这样的新手来说十分的困难,如果设计数据结构这又是一个大难题了。
2016/12/12 17:17:49