在接下來的兩篇文章中,我想談談在 React 應用中使用 Redux-Saga 進行異步 action 管理的基礎和進階方法。我會說明爲何咱們會在 AppsFlyer 項目中使用它,以及它能夠解決什麼問題。前端
本篇文章主要介紹 Redux-Saga 相關的基本概念,下篇專門討論 Redux-Saga 能夠解決哪些問題。請注意:閱讀這兩篇文章,你要對 React 和 Redux 有必定的瞭解。react
爲了理解 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
屬性的值就是 undefined,done
屬性的值就是 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 是經過 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 effect 的 take
方法,這個方法會阻塞 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 被觸發。下次從新進入循環後,也會重複上述過程。
讓咱們一步步地看一下上面的過程:
while(true)
循環,在第 3 行掛起。USER_INTERACTED_WITH_UI_ACTION
這個 action 被觸發。SHOW_LOADING_ACTION
這個 action,而後分配的 reducer 進行處理(reducer 處理後,頁面就會顯示 loading 提示)。SHOW_DATA_ACTION
接收 data 做爲參數被觸發,而後 reducer 就能夠使用這些數據來更新頁面。在這篇文章中,咱們介紹了 Redux-Saga 相關的基本概念,展現瞭如何在 React 應用中使用它。下篇文章中,我會展現在實際應用中使用它得到的價值。
感謝 Yotam Kadishay 和 Liron Cohen。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。