整理和總結官方文檔的中文翻譯版上面對于Redux-sage的介紹
文檔地址:http://leonshi.com/redux-saga-in-chinese/index.html
概述
redux-saga 是一個用于管理Redux 應用異步操作的中間件(又稱異步 action)寡键。 redux-saga 通過創(chuàng)建 Sagas 將所有的異步操作邏輯收集在一個地方集中處理坝辫,可以用來代替 redux-thunk中間件。
Sagas 只會在應用啟動時調用。 Sagas 可以被看作是在后臺運行的進程韭邓。Sagas 監(jiān)聽發(fā)起的 action哮内,然后決定基于這個 action 來做什么:是發(fā)起一個異步調用(比如一個 Ajax 請求)奄容,還是發(fā)起其 他的 action 到 Store康聂,甚至是調用其他的 Sagas。
在 redux-saga 的世界里北滥,所有的任務都通用 yield Effects 來完成(譯注:Effect 可以看作是 redux-saga 的任務單元)刚操。Effects 都是簡單的 Javascript 對象,包含了要被 Saga middleware 執(zhí)行的信息(打個比方再芋,你可以看到 Redux action 其實是一個個包含執(zhí)行信息的對象)
因為使用了 Generator菊霜,redux-saga讓你可以用同步的方式寫異步代碼。
基本概念
1 Saga 輔助函數(shù)
takeEvery :允許多個 fetchData實例同時啟動济赎。在某個特定時刻占卧,我們可以啟動一個新的 fetchData任務遗菠, 盡管之前還有一個或多個 fetchData尚未結束。
takeLatest:只允許執(zhí)行一個 fetchData任務华蜒。并且這個任務是最后被啟動的那個辙纬。 如果之前已經(jīng)有一個任務在執(zhí)行,那之前的這個任務會自動被取消叭喜。
例子:
import { takeEvery } from 'redux-saga'
function* watchFetchData() {
yield* takeEvery('FETCH_REQUESTED', fetchData)
}```
監(jiān)聽 FETCH_REQUESTED action 之后執(zhí)行下面的異步action 任務
import { call, put } from 'redux-saga/effects'
export function* fetchData(action) {
try {
const data = yield call(Api.fetchUser, action.payload.url);
yield put({type: "FETCH_SUCCEEDED", data});
} catch (error) {
yield put({type: "FETCH_FAILED", error});
}
}
#####2 聲明式 Effects
我們從 Generator 里 yield 純 JavaScript 對象以表達 Saga 邏輯贺拣。 我們稱呼那些對象為 *Effect*。Effect 是一個簡單的對象捂蕴,這個對象包含了一些給 middleware 解釋執(zhí)行的信息譬涡。 你可以把 Effect 看作是發(fā)送給 middleware 的指令以執(zhí)行某些操作(調用某些異步函數(shù),發(fā)起一個 action 到 store)啥辨。
一個 Saga 所做的實際上是組合那些所有的 Effect涡匀,共同實現(xiàn)所需的控制流。 最簡單的是只需把 yield 一個接一個地放置溉知,就可對 yield 過的 Effect 進行排序陨瘩。
我們已經(jīng)看到,使用 Effect 諸如 call 和 put级乍,與高階 API 如 takeEvery相結合舌劳,讓我們實現(xiàn)與redux-thunk 同樣的東西, 但又有額外的易于測試的好處玫荣。
######2.1 Effect 概念是如何讓 Sagas 很容易地被測試的
假設我們有一個監(jiān)聽 PRODUCTS_REQUESTED action 的Saga甚淡。每次匹配到 action,它會啟動一個從服務器上獲取產(chǎn)品列表的任務捅厂。
現(xiàn)在要對這段邏輯編寫測試贯卦, 在測試過程中,執(zhí)行真正的服務是一個既不可行也不實用的方法焙贷,所以我們必須 模擬函數(shù)脸侥。也就是說,我們需要將真實的函數(shù)替換為一個假的盈厘,這個假的函數(shù)并不會真的發(fā)送 AJAX 請求而只會檢查是否使用正確的參數(shù)調用方法。模擬使測試更加困難和不可靠官边。
實際上對這段邏輯的測試我們需要的只是保證任務 yield 一個正確的函數(shù)沸手,并且這個函數(shù)有著正確的參數(shù)。 **我們可以僅僅 yield 一條描述函數(shù)調用的信息**
實際中當yield 一個方法時注簿,可以使用call(fn, ...args)這個函數(shù)契吉,**call創(chuàng)建了一條描述結果的信息**,call 只是一個返回純文本對象的函數(shù)诡渴,redux-saga middleware 確保執(zhí)行函數(shù)調用并在響應被 resolve 時恢復 generator捐晶。
代碼示例
import { takeEvery } from 'redux-saga'
import Api from './path/to/api'
function* watchFetchProduts() {
yield* takeEvery('PRODUCTS_REQUESTED',fetchProducts)
}
function* fetchProducts() {
const products = yield call(Api.fetch, '/products')
}
//測試代碼
import { call } from 'redux-saga/effects'
import Api from '...'
const iterator = fetchProducts()
assert.deepEqual(
iterator.next().value, call(Api.fetch, '/products')
)
#####3 發(fā)起 action 到 store
我們想發(fā)起一些action通知
dispatch({type:'PRODUCTS_RECEIVED', products })
為了編寫方便測試的代碼菲语,使用Effect的概念,使用指令的方式來發(fā)起通話惑灵,**只需創(chuàng)建一個對象來指示 middleware 我們需要發(fā)起一些 action山上,然后讓 middleware 執(zhí)行真實的 dispatch。**這種方式我們就可以同樣的方式測試 Generator 的 dispatch:只需檢查 yield 后的 Effect英支,并確保它包含正確的指令佩憾。指令為put
代碼示例
import { call, put } from 'redux-saga/effects'
//...
function* fetchProducts() {
const products = yield call(Api.fetch, '/products')
// 創(chuàng)建并 yield 一個 dispatch Effect
yield put({ type: 'PRODUCTS_RECEIVED', products })
}
//測試代碼
import { call, put } from 'redux-saga/effects'
import Api from '...'
const iterator = fetchProducts()
// 期望一個 call 指令
assert.deepEqual(
iterator.next().value, call(Api.fetch, '/products'),
"fetchProducts should yield an Effect call(Api.fetch,'./products')"
)
// 創(chuàng)建一個假的響應對象
const products = {}
// 期望一個 dispatch 指令
assert.deepEqual(
iterator.next(products).value, put({ type:'PRODUCTS_RECEIVED', products }),
"fetchProducts should yield an Effect put({ type: 'PRODUCTS_RECEIVED', products })"
)
#####4 錯誤處理
我們可以使用熟悉的 try/catch語法在 Saga 中捕獲錯誤。
代碼示例
import Api from './path/to/api'
import { call, put } from 'redux-saga/effects'
//...
function* fetchProducts() {
try {
const products = yield call(Api.fetch, '/products')
yield put({ type: 'PRODUCTS_RECEIVED', products })
} catch(error) {
yield put({ type: 'PRODUCTS_REQUEST_FAILED', error })
}
}
//測試代碼
import { call, put } from 'redux-saga/effects'
import Api from '...'
const iterator = fetchProducts()
// 期望一個 call 指令
assert.deepEqual(
iterator.next().value, call(Api.fetch, '/products'), "fetchProducts should yield an Effect call(Api.fetch, './products')"
)
// 創(chuàng)建一個模擬的 error 對象
const error = {}
// 期望一個 dispatch 指令
assert.deepEqual(
iterator.throw(error).value, put({ type: 'PRODUCTS_REQUEST_FAILED', error }),
"fetchProducts should yield an Effect put({ type: 'PRODUCTS_REQUEST_FAILED', error })"
)
//官網(wǎng)還提供了一種捕捉 Promise 的拒絕操作干花,并將它們映射到一個錯誤字段對象妄帘,可以查閱。個人喜歡第一種錯誤處理方式