Redux-saga 介紹

常用api cheatsheets

前言

本篇重點不在完整介紹 redux-saga的基礎,只針對常用的 API 做整理喔。用了一陣子,真心覺得 redux-saga 比起其他redux library 真的非常推薦啊!

優點

  • 非同步: 解決了redux 非同步的問題。
  • 可讀性較佳: 當商業邏輯較為複雜時,若開發習慣不好,常看到邏輯散落在component, container, 或是 redux中,當程式架構越來越大,也難以一下子就找到對應的邏輯,找 issue也常要翻閱好幾個檔案才找到病因。甚至,API 呼叫也沒有統一管理,沒有一個統一的地方去呼叫。使用 redux-saga 可以輕易地幫助我們將 API 和商業邏輯統整再一起,閱讀上也類似同步的寫法。之前用過 redux-thunk 的架構,將邏輯和 API包在 action中,也讓 action 變得難以維護和閱讀。
  • 可測試性: redux-saga設計上就考慮讓開發者也容易輕鬆的做單元測試,只要了解 generator 的原理,就能簡單地上手。
  • 不需另外客製 middleware。

Setup

創建saga middleware,再將 saga middleware 連接至 redux store。

import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootReducer from './reducers';
import rootSaga from './rootSaga';
export default function configureStore() {
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
rootReducer,
applyMiddleware(sagaMiddleware)
);
  sagaMiddleware.run(rootSaga);
return store;
}

APIs

effect

是一些簡單 javascript 對象,對象包含了要被 middleware 執行的指令。 當 middleware 拿到一个被 saga yield 後的 effect,會暫停 saga直到 effect 執行完成,然後 Saga 才會再度被恢復。middleware 检查每个 yield effect 的類型來决定如何實現 effect。如果其類型是 PUT 那 middleware 會發起 action 到 store。 如果 effect 類型是 CALL 那就會調用给定的函數。

takeEvery(pattern, saga, …args)

saga 將監聽所有與 pattern 相符的 action,當發起一個 action 到 Store 時,takeEvery 將監聽所有與 patttern 相符的action,若相符則啟動對應的新的 saga任務。不過不像 takeLatest 在任何時候只會取最後被啟動的那一次,在那之前的會取消。所以用戶如果狂點按鈕狂發 action,則每次 action 都會被takeEvery 捕捉到,就無法保證執行順序,所以使用時要小心。

import { takeEvery } from 'redux-saga';
function* fetchUser(action) {
...
}
function* watchFetchUser() {
yield* takeEvery('USER_REQUESTED', fetchUser)
}

take(pattern)

創建一條 effect,指示 middleware 等待 Store 上指定的 action。 generator 會暫停,直到一個與 pattern 匹配的 action 被發起。另外一個特別處在於,可以利用 saga 自己主動拉取 (pulling) action 的方式來使用我們熟悉的同步風格来描述複雜的流程和邏輯。

  • 參數為空,或傳入 '*' : 會匹配所有發起的 action。如用這方式來紀錄 log。
  • 參數為函數: action 會在 pattern(action) 返回為 true 時被匹配。如take(action => action.entities) 會匹配那些 entities 字段为真的 action。
  • 參數為字串: action 會在 action.type === pattern 時被匹配。如,take(INCREMENT_ASYNC)
  • 參數為陣列: 會檢查陣列所有項目是否匹配與 action.type 相等的 action。如,take([INCREMENT, DECREMENT]) 會匹配 INCREMENTDECREMENT 類型的 action)。
function* watchGetJobs() {
while (true) {
yield take(types.GET_JOBS_REQUEST);
yield fork(getJobsRequestSaga);
}
}
function* loginFlow() {
while(true) {
yield take('LOGIN')
// ... perform the login logic
yield take('LOGOUT')
// ... perform the logout logic
}
}

put(action)

類似 dispatch effect,但是以聲明式(declarative calls)的方式,主要是方便測試時只需檢查 yield 後的 effect是否為正確的指令。原理為創建一個對象來指示 middleware 要發起一些 action ,再讓 middleware 執行真實的 dispatch,指示 middleware 發起一個 action 到 Store。

call(fn, …args)

創建一個 effect,指示 middleware 調用 fn 函數,並以 args 为参數。要注意的是,call 是一個會阻塞的 effect。即 generator 在調用结束之前不能執行或處理其他事情。callapply 非常適合返回 promise 结果的函數。call 不僅可以用來調用返回 promise 的函數,也可用它来調用其他 generator 函数。另外,call 只是一个返回纯文本對象的函数就非常適合用來測試使用。

fork(fn, …args)

創建一個 effect,指示 middleware 以無阻塞方式執行 fn,在等待 fn 返回结果时,middleware 不會暫停 generator。當我們 fork 一個任務,任務會在後台執行,調用者也可以繼續它自己的流程,而不用等待被 fork 的任務结束。

join(task)

創建一個 effect,指示 middleware 等待之前的 fork 任務返回结果。

cancel(task)

創建一個 effect,指示 middleware 取消之前的 fork 任務返回结果。cancel 是無阻塞 effect。也就是說,generator 將在取消異常被抛出後立即恢復。取消執行中的 generator,middleware 將會抛出SagaCancellationException 的錯誤。

select(selector, …args)

創建一個 effect,指示 middleware 調用提供的選擇器選取 store state 上的數據。如返回 selector(getState(), ...args) 的结果。若参數為空,則返回整個 state。

[…effects]

可同時執行多個任務,就像 Promise.all 的行爲。middleware 只有在所有 effect 成功完成或任一個 effect 被 reject 才會暫停 generator。

import { fetchCustomers, fetchProducts } from './path/to/api'

function* mySaga() {
const [customers, products] = yield [
call(fetchCustomers),
call(fetchProducts)
]
}

race(effects)

不需等待所有任務都完成,只要第一个被 resolve(或 reject)的任務出現即結束。

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

function* fetchPostsWithTimeout() {
const {posts, timeout} = yield race({
posts : call(fetchApi, '/posts'),
timeout : call(delay, 1000)
})

if(posts)
put({type: 'POSTS_RECEIVED', posts})
else
put({type: 'TIMEOUT_ERROR'})
}

Reference

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.