提取未來動作
到目前為止,我們使用輔助效果 takeEvery
來針對每個傳入動作產生新的任務。這與 redux-thunk
的行為有些類似:例如,每當元件呼叫 fetchProducts
動作建立器時,動作建立器將傳送 thunk 以執行控制流程。
實際上,takeEvery
僅僅是內部輔助函式的包裝,並建立在較低層級且較強大的 API 上。在本節中,我們將看到一種新的效果 take
,它透過允許完全控制動作觀察程序,來建構複雜的控制流程。
基本記錄器
我們舉一個 Saga 的基本範例,它會監看傳送至儲存體的所有動作,並將其記錄到主控台。
使用 takeEvery('*')
(搭配萬用字元 *
模式),我們可以捕獲所有派送的動作,而不論它們的類型為何。
import { select, takeEvery } from 'redux-saga/effects'
function* watchAndLog() {
yield takeEvery('*', function* logger(action) {
const state = yield select()
console.log('action', action)
console.log('state after', state)
})
}
現在讓我們看看如何使用 take
效果來實作與上述相同的流程
import { select, take } from 'redux-saga/effects'
function* watchAndLog() {
while (true) {
const action = yield take('*')
const state = yield select()
console.log('action', action)
console.log('state after', state)
}
}
take
就如同我們先前看到的 call
和 put
。它會建立另一個命令物件,用來指示中間件等待特定動作。call
效果所產生的行為,與中間件暫停產生器程式碼,直到承諾事項被解析的狀態相同。在 take
案例中,它會暫停產生器程式碼,直到匹配的動作被派送。在上方的範例中,watchAndLog
會暫停,直到任何動作被派送。
請注意我們如何執行無盡迴圈 while (true)
。請記住,這是一個產生器函式,沒有執行至完成的行為。我們的產生器程式碼會在每個迭代中區塊化,等待動作發生。
使用 take
會對我們撰寫程式碼的方式產生細微影響。在 takeEvery
的案例中,調用的任務無法控制何時會被呼叫。它們會在每個匹配的動作上反覆調用。它們也無法控制何時停止觀察。
在 take
的案例中,控制會被反轉。動作並不會被 推播 至處理器任務;相反的,Saga 會自行 拉取 動作。這樣看起來就好像 Saga 會執行正常的函式呼叫 action = getNextAction()
,而這會在動作被派送時解析。
這種控制反轉允許我們實作非傳統 推播 方法中無法輕鬆執行的控制流程。
針對一個基本範例,假設我們要在 Todo 應用程式中觀察使用者動作,並在使用者建立前三個待辦事項後顯示祝賀訊息。
import { take, put } from 'redux-saga/effects'
function* watchFirstThreeTodosCreation() {
for (let i = 0; i < 3; i++) {
const action = yield take('TODO_CREATED')
}
yield put({type: 'SHOW_CONGRATULATION'})
}
我們會執行 for
迴圈,而不是 while (true)
,它只會迭代三次。在接收前三個 TODO_CREATED
動作後,watchFirstThreeTodosCreation
會導致應用程式顯示祝賀訊息,然後終止。這表示產生器程式碼將被垃圾回收,而且不會再執行任何觀察。
拉取方法的另一個好處是,我們可以用熟悉的同步式樣式來描述我們的控制流程。例如,假設我們要實作一個具有兩個動作的登入流程:LOGIN
和 LOGOUT
。使用 takeEvery
(或 redux-thunk
),我們必須撰寫兩個獨立的任務(或thunk):一個用於 LOGIN
,另一個用於 LOGOUT
。
結果就是我們的邏輯現在分散在兩個地方。為了讓閱讀我們程式碼的人能夠理解它,他們必須閱讀兩個處理常式的程式碼,並且在腦中建立兩者之間的關聯。換句話說,這表示他們必須在腦中重新建立流程模型,以正確的順序在腦中重新排列程式碼中不同地方的邏輯。
使用推模式,我們可以在同一個地方撰寫我們的流程,而不是重複處理同一個動作。
function* loginFlow() {
while (true) {
yield take('LOGIN')
// ... perform the login logic
yield take('LOGOUT')
// ... perform the logout logic
}
}
loginFlow
Saga 更加明確傳達預期的動作順序。它知道 LOGIN
動作應該總是緊接著 LOGOUT
動作,而 LOGOUT
動作總是緊接著 LOGIN
動作(良好的 UI 應該總是強制執行動作的一致順序,方法是隱藏或停用意外的動作)。