09-采用React Saga的心路歷程

我們在之前的實(shí)現(xiàn)中家浇,對于異步 Action 的調(diào)用使用了 redux-saga 中間件妈嘹。thunk 中間件通過增強(qiáng)了返回可調(diào)用函數(shù)的功能成黄,也就允許了我們可以實(shí)現(xiàn)如下所示的異步 Action 休弃。

actions/novel.ts

// 普通 Action
const fetchNovels = createAction(ACTION_TYPES.FETCH_NOVELS);
const fetchNovelsOK = createAction(ACTION_TYPES.FETCH_NOVELS_OK);
const fetchNovelsNG = createAction(ACTION_TYPES.FETCH_NOVELS_NG);

// 異步 Action
export const searchNovels = () => (dispatch: Dispatch) => {
  dispatch(fetchNovels()); // {type: 'FETCH_NOVELS'}
  queryNovels().then(resp => {
    if (resp.isAxiosError) {
      dispatch(fetchNovelsNG(resp)); // {type: 'FETCH_NOVELS_NG', payload: error, error: true}
    } else {
      dispatch(fetchNovelsOK(resp.novel)); // {type: 'FETCH_NOVELS_OK', payload: json}
    }
  });
};

使用 thunk 可以完成我們正常需求,但是它存在一些問題银受。

  • Action 層會隨著項(xiàng)目需求增長而不斷擴(kuò)大践盼。
  • 異步 Action 不能很好的進(jìn)行單元測試。
  • 不能夠更好的組織業(yè)務(wù)的流程控制宾巍,每個(gè)異步 Action 都是相對獨(dú)立的宏侍,無關(guān)聯(lián)的。
  • Action 包含了副作用蜀漆,不能保持為一個(gè) Plain Object。
  • ......(以后發(fā)現(xiàn)繼續(xù)追加)

redux-saga 也是一個(gè)類似 redux-thunk 的增強(qiáng) store 功能的中間件咱旱,它是可測試的确丢,并提供了聲明式指令。saga 使用了 ES6 的 Generator 功能吐限,讓異步的流程更易于讀取鲜侥,寫入和測試,并且將流程控制從Action Creator 中抽出诸典,簡化了 Action 層描函,保持了 Action 層的純凈。

我們將原來定義在 Action 層里的副作用代碼狐粱,轉(zhuǎn)移到 saga 層來實(shí)現(xiàn)舀寓。function* 就是ES6 的 Generator 函數(shù)實(shí)現(xiàn)方式。saga 內(nèi)部的業(yè)務(wù)流程控制都是通過一個(gè)一個(gè)的 yield 來完成的肌蜻,你可以直接 yield 一個(gè) promise(當(dāng)然由于不利于單元測試互墓,不推薦這樣寫),也可以直接使用 saga 所提供的一些申明式的命令蒋搜,也就是 Effect篡撵。每一個(gè) yield 的 Effect 都會傳遞到 redux-saga 中間件被解釋執(zhí)行,如果指令是 promise豆挽,saga 就會暫停等到 promise 返回育谬。接著就執(zhí)行下一個(gè) yield 指令,你也可以通過 if帮哈,for 等控制語句來構(gòu)建更復(fù)雜的流程膛檀。

saga 相關(guān) 基礎(chǔ) Effect

  • put(Action) 創(chuàng)建 dispatch 的 Effect,告訴 middleware 發(fā)起 Action 操作。
  • call(method | generator, arg1, arg2, ...) 告訴 middleware 使用給定的參數(shù) arg1, arg2, ... 調(diào)用給定的 method 或 generator 函數(shù)宿刮,另外一種寫法可以允許我們調(diào)用指定對象的方法 yield call([obj, obj.method], arg1, arg2, ...)互站。適合調(diào)用返回Promise 結(jié)果的函數(shù)。
  • apply(obj, obj.method, [arg1, arg2, ...]) 與 call 指令功能相同僵缺,就寫法不一樣胡桃。
  • select() 返回當(dāng)前完整的 State 樹,與 getState 類似磕潮,也可以指定對應(yīng)的 selector 作為參數(shù)翠胰,來返回指定部分的 State。
  • take(Action | '*') 告訴 middleware 等待一個(gè)指定或者滿足匹配符 * 的 Action自脯。使用 take 就可以組織我們復(fù)雜的流程控制之景。
  • fork(method | generator, arg1, arg2, ...) 相對于take,表示一個(gè)無阻塞調(diào)用膏潮,告訴 middleware 使用給定的參數(shù) arg1, arg2, ... 調(diào)用給定的 method 或 generator 函數(shù)锻狗。
  • cancel(task) 命令 middleware 取消之前的一個(gè) fork 任務(wù)。與之對應(yīng)的cancelled指令可以指定取消任務(wù)需要執(zhí)行的操作焕参。
  • race(effects) 類似Promise.race功能轻纪,在多個(gè) Effects 之間觸發(fā)一個(gè)競賽,誰先完成就結(jié)束整個(gè) Effect叠纷,失敗方自動取消刻帚。

saga 相關(guān) Wraaper Effect

  • takeEvery(Action | '*', function* do(){}) 檢測到指定或者滿足匹配符 * 的 Action 發(fā)起到 store 以后,觸發(fā)后續(xù) do 操作指令涩嚣,允許多個(gè) do 操作同時(shí)發(fā)生崇众。這個(gè)和 redux-thunk 功能相似。
  • takeLatest(Action | '*', function* do(){}) 只相應(yīng)最新的 do 操作航厚。

以上是比較常用的顷歌,還想了解更多請查閱 API 參考
。并且所有的 Effect 都是生成簡單對象后幔睬,發(fā)送給 saga middleware衙吩,由 middleware 來根據(jù)effect 的類型來完成具體的調(diào)用。所以才保證了 saga 來實(shí)現(xiàn)相關(guān)的副作用是可測試的溪窒。

現(xiàn)在開始實(shí)際編碼吧坤塞。首先執(zhí)行命令安裝yarn add redux-saga

在根目錄新建文件夾 sagas,新定義 saga 層來定義我們的業(yè)務(wù)流程澈蚌,新增 novel.ts

import { call, put, take } from "redux-saga/effects";
import { queryNovels } from "../services/novelapi";
import { fetchNovels, fetchNovelsOK, fetchNovelsNG } from "../actions/novel";

export function* watchSearchNovels() {
  // 無限循環(huán)保證 saga 一直在后臺運(yùn)行監(jiān)視
  while (true) {
    // 阻塞直到 fetchNovels Action發(fā)起
    yield take(fetchNovels);
    try {
      // 異步調(diào)用
      const data = yield call(queryNovels);
      // 通知store發(fā)起fetchNovelsOK操作
      yield put(fetchNovelsOK(data.novel));
    } catch (error) {
      // 通知store發(fā)起fetchNovelsNG操作
      yield put(fetchNovelsNG(error));
    }
  }
}

刪除原來 actions/novel.ts 中定義的異步 Action 代碼

import { ACTION_TYPES } from "../constants";
import { createAction } from "redux-actions";

// 普通 Action
export const fetchNovels = createAction(ACTION_TYPES.FETCH_NOVELS); // {type: 'FETCH_NOVELS'}
export const fetchNovelsOK = createAction(ACTION_TYPES.FETCH_NOVELS_OK); // {type: 'FETCH_NOVELS_OK', payload: json}
export const fetchNovelsNG = createAction(ACTION_TYPES.FETCH_NOVELS_NG); // {type: 'FETCH_NOVELS_NG', payload: error, error: true}

configure-store.ts 里移除 thunk 中間件的代碼摹芙,替換為 saga 。

// saga 中間件
const sagaMiddleware = createSagaMiddleware();
// 創(chuàng)建store
export const store = createStore(
  // 跟reducer
  rootReducer,
  // 應(yīng)用中間件
  applyMiddleware(
    sagaMiddleware,
    routerMiddleware(history),
    loggerMiddleware,
    reduxCatch((error: Error) => {
      console.error("Redux Action 調(diào)用出錯(cuò)了");
      console.error(error);
    })
  )
);
// 啟動 saga
sagaMiddleware.run(watchSearchNovels);

reducer 層我們是不用動的宛瞄。如此啟動看看效果吧浮禾。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末交胚,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子盈电,更是在濱河造成了極大的恐慌蝴簇,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件匆帚,死亡現(xiàn)場離奇詭異熬词,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)吸重,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進(jìn)店門互拾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嚎幸,你說我怎么就攤上這事颜矿。” “怎么了嫉晶?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵骑疆,是天一觀的道長。 經(jīng)常有香客問我替废,道長箍铭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任舶担,我火速辦了婚禮,結(jié)果婚禮上彬呻,老公的妹妹穿的比我還像新娘衣陶。我一直安慰自己,他們只是感情好闸氮,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布剪况。 她就那樣靜靜地躺著,像睡著了一般蒲跨。 火紅的嫁衣襯著肌膚如雪译断。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天或悲,我揣著相機(jī)與錄音孙咪,去河邊找鬼。 笑死巡语,一個(gè)胖子當(dāng)著我的面吹牛翎蹈,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播男公,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼荤堪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起澄阳,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤拥知,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后碎赢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體低剔,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年揩抡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了户侥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,117評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡峦嗤,死狀恐怖蕊唐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情烁设,我是刑警寧澤替梨,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站装黑,受9級特大地震影響副瀑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜恋谭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一糠睡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧疚颊,春花似錦狈孔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至其掂,卻和暖如春油挥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背款熬。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工深寥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人贤牛。 一個(gè)月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓翩迈,卻偏偏與公主長得像,于是被迫代替她去往敵國和親盔夜。 傳聞我的和親對象是個(gè)殘疾皇子负饲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評論 2 355

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

  • 最近項(xiàng)目用了dva堤魁,dva對于異步action的處理是用了redux-saga,故簡單學(xué)習(xí)了下redux-saga...
    笨人不能懶閱讀 2,836評論 0 5
  • Redux Saga 前言 使用Saga也有一段時(shí)間了返十,剛開始ReduxThunk轉(zhuǎn)換到Saga的適應(yīng)期中還是比較...
    Rocky_Wong閱讀 5,338評論 1 12
  • Redux-saga 概述 redux-saga是一個(gè)用于管理redux應(yīng)用異步操作的中間件妥泉,redux-saga...
    woow_wu7閱讀 51,703評論 11 41
  • 看到這篇文章build an image gallery using redux saga,覺得寫的不錯(cuò)洞坑,長短也適...
    smartphp閱讀 6,158評論 1 29
  • 1. redux-thunk處理副作用的缺點(diǎn) 1.1 redux的副作用處理 redux中的數(shù)據(jù)流大致是: UI—...
    Grace_ji閱讀 3,542評論 0 14