跳到主要內容

提取未來動作

到目前為止,我們使用輔助效果 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 就如同我們先前看到的 callput。它會建立另一個命令物件,用來指示中間件等待特定動作。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 會導致應用程式顯示祝賀訊息,然後終止。這表示產生器程式碼將被垃圾回收,而且不會再執行任何觀察。

拉取方法的另一個好處是,我們可以用熟悉的同步式樣式來描述我們的控制流程。例如,假設我們要實作一個具有兩個動作的登入流程:LOGINLOGOUT。使用 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 應該總是強制執行動作的一致順序,方法是隱藏或停用意外的動作)。