Redux-Saga 初識和總結

做者介紹:羅雪婧,美團點評前端工程師,3年 Web 前端開發經驗,如今是美團點評點餐團隊的一員。javascript

1、Redux-Saga介紹

redux-saga 是一個旨在於在React/Redux應用中更好、更易地解決異步操做(action)的庫。主要模塊是 saga 會像一個分散的支線在你的應用中單獨負責解決異步的action(相似於後臺運行的進程)。詳細移步:Redux-saga前端

redux-saga至關於在Redux原有數據流中多了一層,對Action進行監聽,捕獲到監聽的Action後能夠派生一個新的任務對state進行維護(固然也不是必需要改變State,能夠根據項目的需求設計),經過更改的state驅動View的變動。圖以下所示:java

用過redux-thunk的人會發現,redux-saga 其實和redux-thunk作的事情相似,都是能夠處理異步操做和協調複雜的dispatch。不一樣點在於:git

  • Sagas 是經過 Generator 函數來建立的,意味着能夠用同步的方式寫異步的代碼;
  • Thunks 是在 action 被建立時才調用,Sagas 在應用啓動時就開始調用,監聽action 並作相應處理; (經過建立 Sagas 將全部的異步操做邏輯收集在一個地方集中處理)
  • 啓動的任務能夠在任什麼時候候經過手動取消,也能夠把任務和其餘的 Effects 放到 race 方法裏能夠自動取消;

2、入門demo

redux-saga-beginner-tutorialgithub

$ git clone https://github.com/HelianXJ/redux-saga-beginner-tutorial.git
$ git checkout redux-tool-saga // 切到有redux tool的分支配合chorme 的 Redux DevTools 工具查看邏輯更清晰

$ npm i  //下載依賴
$ npm run hello //先看項目文件中的hello sagas複製代碼

啓動server成功後view-on: http://172.22.32.14:9966/ npm

可看到以下界面,一個簡單的例子,點擊say hello按鈕展現 hello,點擊say goodbye按鈕展現goodbye。可注意看右邊欄的Action變化和console控制檯的輸出。redux


sagas.js 關鍵代碼前端工程師

import { takeEvery } from 'redux-saga';

export function* helloSaga() {
  console.log('Hello Sagas!');
}

export default function* watchIncrementAsync() {
    yield* takeEvery('SAY_HELLO', helloSaga);
}複製代碼

這裏sagas建立了一個watchIncrementAsync 監聽SAY_HELLO的Action,派生一個新的任務——在控制檯打印出「Hello Sagas!」經過這例子能夠理解redux-saga大體作的事情。併發

該項目中還有一個計數器的簡單例子。異步

$ npm start //便可查看Counter的例子複製代碼

sagas.js關鍵代碼

// 一個工具函數:返回一個 Promise,這個 Promise 將在 1 秒後 resolve
export const delay = ms => new Promise(resolve => setTimeout(resolve, ms))

// Our worker Saga: 將異步執行 increment 任務
export function* incrementAsync() {
    yield delay(1000);
      yield put({ type: 'INCREMENT' });
}

// Our watcher Saga: 在每一個 INCREMENT_ASYNC action 調用後,派生一個新的 incrementAsync 任務
export default function* watchIncrementAsync() {
      yield* takeEvery('INCREMENT_ASYNC', incrementAsync);
}複製代碼

計數器例子的單元測試 sagas.spec.js 關鍵代碼

import test from 'tape';
import { put, call } from 'redux-saga/effects'
import { incrementAsync, delay } from './sagas'

test('incrementAsync Saga test', (assert) => {
  const gen = incrementAsync()

  assert.deepEqual(
    gen.next().value,
    call(delay, 1000),
    'incrementAsync Saga must call delay(1000)'
  )

  assert.deepEqual(
    gen.next().value,
    put({type: 'INCREMENT'}),
    'incrementAsync Saga must dispatch an INCREMENT action'
  )

  assert.deepEqual(
    gen.next(),
    { done: true, value: undefined },
    'incrementAsync Saga must be done'
  )

  assert.end()
});複製代碼

因爲redux-saga是用ES6的Generators實現異步,incrementAsync 是一個 Generator 函數,因此當咱們在 middleware 以外運行它,會返回一個易預見的遍歷器對象, 這一點應用在單元測試中更容易寫unit。

redux-saga能作的不僅是能夠作以上例子的事情。

實際上 redux-saga 全部的任務都通用 yield Effects 來完成。它爲各項任務提供了各類 Effect 建立器,能夠是:

  • 調用一個異步函數;
  • 發起一個 action 到 Store;
  • 啓動一個後臺任務或者等待一個知足某些條件的將來的 action。

3、redux-sagas的使用

  • 組合sagas (yield Sagas) —— 實際上和redux-thunk 的dispatch 一個action相似
function* fetchPosts() {
  yield put( actions.requestPosts() )
  const products = yield call(fetchApi, '/products')
  yield put( actions.receivePosts(products) )
}

function* watchFetch() {
  while ( yield take(FETCH_POSTS) ) {
    yield call(fetchPosts) // waits for the fetchPosts task to terminate
  }
}複製代碼

當 yield 一個 call 至 Generator,Saga 將等待 Generator 處理結束, 而後以返回的值恢復執行

  • 任務取消 —— 一旦任務被 fork,可使用 yield cancel(task) 來停止任務執行。取消正在運行的任務,將拋出 SagaCancellationException 錯誤。

  • 同時執行多個任務
const [users, repos] = yield [
     call(fetch, '/users'),
     call(fetch, '/repos')
 ]複製代碼
  • 使用輔助函數管理 Effects 之間的併發。
function* takeEvery(pattern, saga, ...args) {
  while(true) const action = yield take(pattern)
    yield fork(saga, ...args.concat(action))
  }
}複製代碼

3、Redux-Saga優勢

  • 流程拆分更細,異步的action 以及特殊要求的action(更復雜的action)都在sagas中作統一處理,流程邏輯更清晰,模塊更乾淨;
  • 以用同步的方式寫異步代碼,能夠作一些async 函數作不到的事情 (無阻塞併發、取消請求)
  • 能容易地測試 Generator 裏全部的業務邏輯
  • 能夠經過監聽Action 來進行前端的打點日誌記錄,減小侵入式打點對代碼的侵入程度

4、帶來的問題和可接受性

  • action 任務拆分更細,原有流程上至關於多了一個環節。對開發者的設計和抽象拆分能力更有要求,代碼複雜性也有所增長。
  • 異步請求相關的問題較難調試排查
相關文章
相關標籤/搜索