[譯] Don’t call me, I’ll call you:使用 Redux-Saga 管理 React 應用中的異步 action (上)

Don’t call me, I’ll call you:使用 Redux-Saga 管理 React 應用中的異步 action (上)

在接下來的兩篇文章中,我想談談在 React 應用中使用 Redux-Saga 進行異步 action 管理的基礎和進階方法。我會說明爲何咱們會在 AppsFlyer 項目中使用它,以及它能夠解決什麼問題。前端

本篇文章主要介紹 Redux-Saga 相關的基本概念,下篇專門討論 Redux-Saga 能夠解決哪些問題。請注意:閱讀這兩篇文章,你要對 ReactRedux 有必定的瞭解。react

Generators 先行!

爲了理解 Sagas,咱們首先要理解什麼是 Generator。下面是 MDN 對 Generator 的描述:android

Generator 是在執行時能暫停,後面又能從暫停處繼續執行的函數。它的上下文會在繼續執行時保存。ios

你能夠把 Generator 理解成一種遍歷器對象生成函數,(譯註:Generator 執行後返回的遍歷器對象)提供一個 next 方法。執行這個方法就會返回下一個狀態,或者返回遍歷結束的狀態。這就須要 Generator 可以維護內部狀態。git

下面是一個基本的 Generator 示例,它生成的遍歷器對象會返回幾個字符串:github

function* namesEmitter() {
  yield "William";
  yield "Jacob";
  return "Daniel";
}

// 執行 Generator
var generator = namesEmitter();

console.log(generator.next()); // prints {value: "William", done: false}

console.log(generator.next()); // prints {value: "Jacob", done: false}

console.log(generator.next()); // prints {value: "Daniel", done: true}
複製代碼

next 方法的返回值結構很是簡單 — 只要咱們經過 yield/return 返回值,這個返回值就是 value 屬性的值。若是咱們沒有返回值,value 屬性的值就是 undefineddone 屬性的值就是 true。 還有一點值的注意的是,執行 namesEmitter 後,函數會在調用 yield 的地方停下來。當咱們調用 next 方法後,函數會繼續執行,直到遇到下一個 yield。若是咱們調用了 return 語句或者函數執行完畢,done 屬性就會爲真。redux

若是狀態序列的長度不肯定時,咱們能夠用下面的方法來寫:後端

var results = generator.next();
while(!results.done){
 console.log(results.value);
 results = generator.next();
}
console.log(results.value);
複製代碼

什麼是 Sagas?

Sagas 是經過 Generator 函數來建立的。官方文檔 的解釋以下:api

Saga 就像應用中的一個獨立線程,徹底負責管理異步 action。bash

你能夠把 Saga 想象成一個以最快速度不斷地調用 next 方法並嘗試獲取全部 yield 表達式值的線程。你可能會問這和 React 有什麼關係,爲何要使用它,因此首先來看看如何在 React & Redux 應用使用 Saga:

在 React & Redux 應用中,一個常見的用法從調用一個 action 開始。被分配用來處理這個 action 的 reducer 會使用新的 state 更新 store,隨後視圖就會被更新渲染。 若是一個 Saga 被分配用來處理這個 action — 這個 action 一般就是個異步 action(好比一個對服務端的請求),一旦這個 action 完成後,Saga 會調用另外一個 action 讓 reducer 進行處理。

常見用例

咱們能夠經過一個常見流程來講明: 用戶與頁面進行交互,這個交互動做會觸發一個從服務端請求數據的動做(此時頁面顯示 loading 提示),最終咱們用請求回來的數據去渲染頁面的內容。 讓咱們爲每步建立一個 action,而後用 Redux-Saga 實現一個簡化的版本以下:

// saga.js
import { take } from 'redux-saga/effects'

function* mySaga(){ 
    yield take(USER_INTERACTED_WITH_UI_ACTION);
}
複製代碼

這個 Saga 的函數名叫作 mySaga。它調用了 Redux-Saga effecttake 方法,這個方法會阻塞 Saga 的執行,直到有人調用了做爲參數的那個 action,Saga 的執行也會結束,就像咱們前面看到的 Generator 同樣(done 變爲 true)。

如今咱們要讓頁面展現 loading 提示來響應這個 action。能夠經過 put 方法調用另外一個 action,而後分配 reducer 來處理,從而完成上述功能。以下:

// saga.js
import { take, put } from 'redux-saga/effects'

function* mySaga(){ 
    yield take(USER_INTERACTED_WITH_UI_ACTION);
    yield put(SHOW_LOADING_ACTION, {isLoading: true});
}

// reducer.js
...
case SHOW_LOADING_ACTION: (state, isLoading) => {
    return Object.assign({}, state, {showLoading: isLoading});
}
...
複製代碼

下一步是調用 call 方法,它接收一個函數和一組參數,使用這些參數來執行這個函數。咱們給 call 方法傳遞一個請求服務端並返回一個 Promise 的 GET 函數,它會保存請求結果:

// saga.js
import { take, put, call } from 'redux-saga/effects'

function* mySaga(){ 
    yield take(USER_INTERACTED_WITH_UI_ACTION);
    yield put(SHOW_LOADING_ACTION, {isLoading: true});
    const data = yield call(GET, 'https://my.server.com/getdata');
    yield put(SHOW_DATA_ACTION, {data: data});
}

// reducer.js
...
case SHOW_DATA_ACTION: (state, data) => {
    return Object.assign({}, state, {data: data, showLoading: false};
}
...
複製代碼

經過調用 SHOW_DATA_ACTION 來用接收的數據更新頁面。

剛剛發生了什麼?

應用啓動後,全部的 Sagas 都會被執行,你能夠認爲一直在調用 next 方法直到結束。take 方法相似於線程掛起的做用,一旦調用了USER_INTERACTED_WITH_UI_ACTION,線程就會恢復執行。

而後,咱們繼續調用 SHOW_LOADING_ACTION,reducer 會處理這個 action。因爲 Saga 還在繼續運行,call 方法會發起對服務端的請求,Saga 會在再次掛起,直到請求結束。

每次都使用

在上面的例子中,Saga 只處理了一個用戶交互的 action,由於咱們用 put 方法執行了 SHOW_DATA_ACTION 這個 action,而後後面就沒有 yield 了(done 就是 true 了對吧?)。

若是咱們但願在每次調用 USER_INTERACTED_WITH_UI_ACTION 這個 action 的時候,都會執行這一系列的 actions,咱們能夠用 while(true) 語句來包裹 Saga 內部的邏輯代碼。完整代碼以下:

// saga.js
import { take, put, call } from 'redux-saga/effects'

1. function* mySaga(){
2.   while (true){
3.    yield take(USER_INTERACTED_WITH_UI_ACTION);
4.    yield put(SHOW_LOADING_ACTION, {isLoading: true});
5.    const data = yield call(GET, 'https://my.server.com/getdata');
6.    yield put(SHOW_DATA_ACTION, {data: data});
7.  }
8. }

// reducer.js
...
case SHOW_LOADING_ACTION: (state, isLoading) => {
    return Object.assign({}, state, {showLoading: isLoading});
},
case SHOW_DATA_ACTION: (state, data) => {
    return Object.assign({}, state, {data: data, showLoading: false};
}
...
複製代碼

這個無限循環不會形成堆棧溢出,也不會使你的應用崩潰!由於 take 方法就像線程掛起同樣,mySaga 執行後會一直保持 pending 狀態,直到那個 action 被觸發。下次從新進入循環後,也會重複上述過程。

讓咱們一步步地看一下上面的過程:

  1. 應用啓動,執行全部 Sagas。
  2. mySaga 運行,進入 while(true) 循環,在第 3 行掛起。
  3. USER_INTERACTED_WITH_UI_ACTION 這個 action 被觸發。
  4. Saga 的線程激活,執行第 4 行,觸發 SHOW_LOADING_ACTION 這個 action,而後分配的 reducer 進行處理(reducer 處理後,頁面就會顯示 loading 提示)。
  5. 發送一個請求到服務端(第 5 行),而後會再次掛起,直到請求的 Promise 變爲 resolved,請求結果的數據會賦值給 data 變量。
  6. SHOW_DATA_ACTION 接收 data 做爲參數被觸發,而後 reducer 就可使用這些數據來更新頁面。
  7. 再次進入循環,回到第 2 步。

接下來

在這篇文章中,咱們介紹了 Redux-Saga 相關的基本概念,展現瞭如何在 React 應用中使用它。下篇文章中,我會展現在實際應用中使用它得到的價值。

感謝 Yotam KadishayLiron Cohen


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索