#6 異步actions & react-thunk

asynchronous action creator 做數(shù)據(jù)取回纠炮,至少發(fā)送3個(gè)不同的actions:

  1. an action 查詢store, 請求開始(request began)
  2. an action 查詢store, 請求成功的完成(request finished successfully)
  3. an action 查詢store, 請求失敗(request failed)

下面以pro react中的示例作介紹,這個(gè)項(xiàng)目主要是查詢飛機(jī)航班信息和飛機(jī)票信息了解具體操作流程:

1.目錄結(jié)構(gòu)

  • app/
    • actions/
    • api/
    • components/
    • reducers/
    • store/
    • index.js
    • constants.js

其中 粗體帶/ 表示文件夾雨饺, 斜體 表示文件, app/ 文件夾在根目錄下。

2.定義constants.js

這個(gè)常量文件主要是項(xiàng)目中發(fā)送請求的類型,通常都是一邊開發(fā),一邊根據(jù)需求來實(shí)現(xiàn)的牵敷,這里由于是示例,暫且先一次性寫出來:

// constants.js
/*
 *  REQUEST_AIRPORTS: 表示應(yīng)用開始取回航班信息
 *  RECEIVE_AIRPORTS: 通過負(fù)載(preloaders)來表示異步取回信息失敗或者成功(注意這個(gè)動(dòng)作表示2種狀態(tài))
 *  CHOOSE_AIRPORT  : 選擇起點(diǎn)和終點(diǎn)的操作法希,同步操作
 *  REQUEST_TICKETS : 表示應(yīng)用請求票務(wù)信息
 *  RECEIVE_TICKETS : 異步取回票務(wù)信息成功或失敗(注意這個(gè)動(dòng)作表示2種狀態(tài)枷餐,成功或失敗)
 */

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.創(chuàng)建輔助API函數(shù),用來獲取服務(wù)器中的數(shù)據(jù)

這些數(shù)據(jù)APIs現(xiàn)在都由示例提供苫亦,通過上面的示例我們知道有2次異步的操作: 取回航班信息和取回票務(wù)信息毛肋。

本例使用 fetch進(jìn)行ajax操作,返回一個(gè)promise,在 api/文件夾下:

// api/AirCheapAPI.js

import 'whatwg-fetch';

// 用fetch取回?cái)?shù)據(jù)
// 對數(shù)據(jù)進(jìn)行json()格式化
// 整個(gè)函數(shù)返回一個(gè)promise, 等待進(jìn)一步操作
const AirCheapAPI = {
  // 取回航班信息  
  fetchAirports() {
    return fetch('https://aircheapapi.pro-react.com/airports')
            .then(response => response.json());
  }屋剑,

  // 取回票務(wù)信息
  fetchTickets(origin, destination) {
    return fetch(`https://aircheapapi.pro-react.com/tickets?origin=${origin}&destination=${destination}`)
            .then(response => response.json());
  }
}

export default AirCheapAPI;

4.AirportActionCreators

根據(jù)常量中的動(dòng)作類型和需求來定義actions, 再將其寫成函數(shù)的形式润匙,方便傳入到數(shù)據(jù)中,在實(shí)際的開發(fā)中,這些不可能一蹴而就唉匾,往往都是按需開發(fā)孕讳。

// actions/AirportActionCreators.js

import {
  REQUEST_AIRPORTS,
  RECEIVE_AIRPORTS,
  CHOOSE_AIRPORT,
  REQUEST_TICKETS,
  RECEIVE_TICKETS
} from '../constants';
import AirCheapAPI from '../api/AirCheapAPI';

const AirportActionCreators = {
  // 獲取航班信息
  // 異步操作, 返回一個(gè)函數(shù)
  // Thunk action creator
  fetchAirportsInfo(origin, destination) {
    return (dispatch) => {
      // 本地發(fā)送請求航班信息
      dispatch({ type: REQUEST_AIRPORTS });
      
      // promise
      // 取回?cái)?shù)據(jù)成功(success), 則發(fā)送RECEIVE_AIRPORTS動(dòng)作和preload
      // success, airports為負(fù)載
      // 取回?cái)?shù)據(jù)失敗(error), 則發(fā)送RECEIVE_AIRPORTS動(dòng)作和preload
      AirCheapAPI.fetchAirports().then(
        (success) => dispatch({ type: RECEIVE_AIRPORTS, success: true, airports }),
        (error) => dispatch({ type: RECEIVE_AIRPORTS, success: false })
      )
    };
  },

  // 選擇出發(fā)地和目的地
  // 同步操作巍膘, 返回一個(gè)action 對象
  // target厂财, code 都是發(fā)送動(dòng)作時(shí)的負(fù)載
  chooseAirport(target, airport) {
    return {
      type: CHOOSE_AIRPORT,
      target,
      code: airport ? airport.value : ''
    }
  },

  // 獲取票務(wù)信息
  // 異步操作, 返回一個(gè)函數(shù)
  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;

通過上面可以看出峡懈,異步操作一般調(diào)用返回一個(gè)函數(shù)璃饱,調(diào)用輔助api,而同步操作肪康,返回一個(gè)action對象即可

4.reducers

寫完actionCreators之后就該寫reducers(通常需要知道數(shù)據(jù)結(jié)構(gòu)),一般將大的reducers工具功能劃分成小的單個(gè)reducer荚恶,最后再合并成一個(gè)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:
      // 更新狀態(tài)樹中的route狀態(tài)
      // 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主要用于清空狀態(tài)樹中tickets的內(nèi)容
    case REQUEST_TICKETS:
      return [];
    case RECEVIE_TICKETS:
      return action.tickets;
    default:
      return state;
  }
}

export default tickets;

將多個(gè)reducers合并成一個(gè)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了,因?yàn)楫惒降脑蛭肀罚枰砑又虚g件react-thunk,另外這里自己寫一個(gè)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;

到目前為止,所有的基本框架已經(jīng)搭建好了茉帅,剩下的就是去寫components/中的組件了,當(dāng)然這只是示例锭弊,實(shí)際開發(fā)中肯定要先寫組件堪澎,然后依據(jù)需求添加上面的api, constants, actions, reducers, store。

總結(jié)

寫這個(gè)主要是為了熟悉開發(fā)流程味滞,以及異步操作的處理當(dāng)中要注意的一些事項(xiàng)和規(guī)范樱蛤。真實(shí)項(xiàng)目目前做的比較少,大致流程可以理解為:

  1. 寫設(shè)計(jì)組件結(jié)構(gòu)剑鞍,由幾部分組件昨凡,寫出presentation components(視覺組件)
  2. 然后初步列舉出所需要的功能,將功能的實(shí)現(xiàn)以動(dòng)作的形式命名蚁署,寫入到constants.js中
  3. 列舉出可能會(huì)出項(xiàng)的異步操作便脊,寫入到輔助api中
  4. 根據(jù)動(dòng)作類型寫出actionCreator, 及對應(yīng)的reducer
  5. 將routeReducer寫入store
  6. 再寫container components(容器組件), 然后調(diào)用store中的所需要的狀態(tài)

當(dāng)然狀態(tài)樹(state tree)這一點(diǎn)上對于我這樣的新手來說十分的困難,如果設(shè)計(jì)數(shù)據(jù)結(jié)構(gòu)這又是一個(gè)大難題了光戈。

2016/12/12 17:17:49

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末哪痰,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子久妆,更是在濱河造成了極大的恐慌晌杰,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件筷弦,死亡現(xiàn)場離奇詭異肋演,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)烂琴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門爹殊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人监右,你說我怎么就攤上這事边灭。” “怎么了健盒?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵绒瘦,是天一觀的道長。 經(jīng)常有香客問我扣癣,道長惰帽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任父虑,我火速辦了婚禮该酗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己呜魄,他們只是感情好悔叽,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著爵嗅,像睡著了一般娇澎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上睹晒,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天趟庄,我揣著相機(jī)與錄音,去河邊找鬼伪很。 笑死戚啥,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的锉试。 我是一名探鬼主播猫十,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼呆盖!你這毒婦竟也來了炫彩?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤絮短,失蹤者是張志新(化名)和其女友劉穎江兢,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體丁频,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡杉允,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了席里。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叔磷。...
    茶點(diǎn)故事閱讀 40,675評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖奖磁,靈堂內(nèi)的尸體忽然破棺而出改基,到底是詐尸還是另有隱情,我是刑警寧澤咖为,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布秕狰,位于F島的核電站,受9級特大地震影響躁染,放射性物質(zhì)發(fā)生泄漏鸣哀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一吞彤、第九天 我趴在偏房一處隱蔽的房頂上張望我衬。 院中可真熱鬧叹放,春花似錦、人聲如沸挠羔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽破加。三九已至糕档,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拌喉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工俐银, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留尿背,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓捶惜,卻偏偏與公主長得像田藐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子吱七,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容