Redux Saga: Padrões que usamos

Nesta postagem, vamos mergulhar no Redux Saga, que é uma estrutura JavaScript usada para lidar com efeitos colaterais em nosso aplicativo da web. Em um post anterior, apresentei-nos os principais conceitos do Redux Saga . Se você está apenas começando com o Redux Saga, recomendo começar por aí.
Os tópicos específicos que discutiremos são:
- Executando uma saga uma vez com
take
, em vez detakeLatest
outakeEvery
- Aguardando a primeira ação finalizada com
race
- Compartilhamento de serviços em seu aplicativo com
getContext
Executando uma saga uma vez com take
, em vez de takeLatest
outakeEvery
Dentro de nossas sagas de fatias, geralmente usamos takeLatest
ou takeEvery
, que abordei em meu post anterior . Para a maioria dos casos de uso, queremos considerar cada ação detectada.
Mas e se estivermos interessados apenas na primeira ocorrência de uma ação, como a primeira vez que o usuário interage com um formulário? Como seria essa implementação em nossa saga de fatias? A API do Redux Saga não oferece um método específico para isso, mas, como veremos, há uma maneira direta de fazer isso.
Primeiro, algum código clichê:
function* changePasswordSubmittedHandler() {
// ...
}
function* changePasswordSaga() {
yield all([
takeLatest(CHANGE_PASSWORD_SUBMITTED, changePasswordSubmittedHandler)
])
}
Agora vamos adicionar um manipulador que queremos que seja acionado apenas uma vez.
function* changePasswordSubmittedHandler() {
// ...
}
function* changePasswordFormFocusedHandler() {
yield take(CHANGE_PASSWORD_FORM_FOCUSED)
// ...
}
function* changePasswordSaga() {
yield all([
takeLatest(CHANGE_PASSWORD_SUBMITTED, changePasswordSubmittedHandler),
changePasswordFormFocusedHandler()
])
}
O que está acontecendo aqui? A execução changePasswordFormFocusedHandler
por si só, em vez de com takeLatest
ou takeEvery
nos permite executar o manipulador uma vez e apenas uma vez. A take
instrução no início faz com que a função seja pausada . Apenas uma vez que nosso aplicativo detecta a CHANGE_PASSWORD_FORM_FOCUSED
vontade, ele retoma e executa o restante da função.
Uma abordagem alternativa seria incluir um sinalizador no estado global do seu aplicativo que você pode inverter assim que a primeira ocorrência ocorrer. Mas se você quiser evitar adicionar propriedades ao estado, essa abordagem funciona bem.
Aguardando a primeira ação finalizada comrace
Às vezes, queremos ouvir várias ações e executar etapas específicas, dependendo de qual dessas ações foi concluída primeiro. Por exemplo, quando um usuário envia o pagamento de um pedido, a transação pode ser concluída com êxito ou pode falhar por vários motivos. Para este cenário, nos voltamos para o race
combinador de efeitos. Redux Saga refere-se a race
e all
como combinadores de efeitos porque ambos aceitam 1 ou mais efeitos e os manipulam simultaneamente.
Vejamos como race
funciona:
function* paymentSubmittedHandler() {
const { failed, finished, cancelled } = yield race({
failed: take(PAYMENT_SUBMISSION_FAILED),
finished: take(PAYMENT_SUBMISSION_FINISHED),
cancelled: take(PAYMENT_SUBMISSION_CANCELLED)
})
if (finished) {
// do something
}
if (cancelled || failed) {
// do something
}
}
Essa é uma abordagem útil para lidar com solicitações assíncronas. Usamos corrida para os seguintes cenários:
- carregando e inicializando SDKs e bibliotecas
- buscando segmentos de informações do usuário
- lidando com redirecionamentos
- validação de pesquisa
- gerenciamento de token
- Eventos de IU como o abaixo:
const { closed } = yield race({
confirmed: take(AGE_VERIFICATION_MODAL_CONFIRMED),
closed: take(AGE_VERIFICATION_MODAL_CLOSED)
})
Se quisermos ter acesso a determinados serviços (ou contextos) que podem ser compartilhados ao longo de nossas sagas, podemos utilizar getContext
. Com alguma configuração rápida, podemos facilmente ter acesso a métodos que podem chamar APIs, LocalStorage, mecanismos de registro e middleware.
import createSagaMiddleware from 'redux-saga'
const cookieStorage = {
save: value => {
// save the cookie
}
}
const logger = {
logInfo: info => {
// log the info
}
}
const sagaMiddleware = createSagaMiddleware({
context: {
cookieStorage,
logger
}
})
sagaMiddleware.run(rootSaga)
Feito isso, podemos acessar esses contextos em nossas sagas de fatias com getContext
.
function* cookiePreferenceStorageSaga() {
try {
const { cookieStorage } = yield getContext('cookieStorage')
const key = 'foobar'
yield call(cookieStorage.save, key)
yield put(cookiePreferenceStorageSucceeded())
} catch (error) {
const logger = yield getContext('logger')
logger.logInfo('Cookie preference storage error', { error })
}
}
Conclusão
Esses padrões bacanas servem como abordagens confiáveis para lidar com um ambiente dinâmico como o estado global de um aplicativo da Web massivo. Talvez em uma postagem futura eu documente mais alguns padrões que usamos no Just Eat Takeaway. Deixe-me saber se você gostaria de ler sobre isso!