Redux Saga: Padrões que usamos

Dec 05 2022
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 os principais conceitos do Redux Saga.

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 de takeLatestoutakeEvery
  • Aguardando a primeira ação finalizada comrace
  • Compartilhamento de serviços em seu aplicativo comgetContext

Executando uma saga uma vez com take, em vez de takeLatestoutakeEvery

Dentro de nossas sagas de fatias, geralmente usamos takeLatestou 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 changePasswordFormFocusedHandlerpor si só, em vez de com takeLatestou takeEverynos permite executar o manipulador uma vez e apenas uma vez. A takeinstrução no início faz com que a função seja pausada . Apenas uma vez que nosso aplicativo detecta a CHANGE_PASSWORD_FORM_FOCUSEDvontade, 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 racecombinador de efeitos. Redux Saga refere-se a racee allcomo combinadores de efeitos porque ambos aceitam 1 ou mais efeitos e os manipulam simultaneamente.

Vejamos como racefunciona:

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!