#6 异步actions & react-thunk

asynchronous action creator 做数据取回,至少发送3个不同的actions:

  1. an action 查询store, 请求开始(request began)
  2. an action 查询store, 请求成功的完成(request finished successfully)
  3. 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_AIRPORTSREQUEST_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。

总结

写这个主要是为了熟悉开发流程,以及异步操作的处理当中要注意的一些事项和规范。真实项目目前做的比较少,大致流程可以理解为:

  1. 写设计组件结构,由几部分组件,写出presentation components(视觉组件)
  2. 然后初步列举出所需要的功能,将功能的实现以动作的形式命名,写入到constants.js中
  3. 列举出可能会出项的异步操作,写入到辅助api中
  4. 根据动作类型写出actionCreator, 及对应的reducer
  5. 将routeReducer写入store
  6. 再写container components(容器组件), 然后调用store中的所需要的状态

当然状态树(state tree)这一点上对于我这样的新手来说十分的困难,如果设计数据结构这又是一个大难题了。

2016/12/12 17:17:49

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容