Redux Saga: Patrones que usamos

Dec 05 2022
En esta publicación, nos sumergiremos en Redux Saga, que es un marco de JavaScript utilizado para manejar los efectos secundarios en nuestra aplicación web. En una publicación anterior, nos presentó los conceptos principales de Redux Saga.

En esta publicación, nos sumergiremos en Redux Saga, que es un marco de JavaScript utilizado para manejar los efectos secundarios en nuestra aplicación web. En un post anterior, nos introdujimos en los principales conceptos de Redux Saga . Si recién está comenzando con Redux Saga, le recomiendo comenzar allí.

Los temas específicos que trataremos son:

  • Ejecutar una saga una vez con take, en lugar de takeLatestotakeEvery
  • Esperando la primera acción terminada conrace
  • Compartir servicios a través de su aplicación congetContext

Ejecutar una saga una vez con take, en lugar de takeLatestotakeEvery

Dentro de nuestras sagas de rebanadas, generalmente usamos takeLatesto takeEvery, que cubrí en mi publicación anterior . Para la mayoría de los casos de uso, queremos considerar cada acción que se detecta.

Pero, ¿y si solo nos interesa la primera aparición de una acción, como la primera vez que el usuario interactúa con un formulario? ¿Cómo sería esa implementación en nuestra saga de rebanadas? La API de Redux Saga no ofrece un método específico para esto, pero como estamos a punto de ver, hay una forma sencilla de hacerlo.

Primero, un código repetitivo:

function* changePasswordSubmittedHandler() {
  // ...
}
function* changePasswordSaga() {
  yield all([
    takeLatest(CHANGE_PASSWORD_SUBMITTED, changePasswordSubmittedHandler)
  ])
}

Ahora agreguemos un controlador que solo queremos que se active una vez.

function* changePasswordSubmittedHandler() {
  // ...
}
function* changePasswordFormFocusedHandler() {
  yield take(CHANGE_PASSWORD_FORM_FOCUSED)
  // ...
}

function* changePasswordSaga() {
  yield all([
    takeLatest(CHANGE_PASSWORD_SUBMITTED, changePasswordSubmittedHandler),
    changePasswordFormFocusedHandler()
  ])
}

¿Que esta pasando aqui? Ejecutarse changePasswordFormFocusedHandlersolo, en lugar de con takeLatesto takeEverynos permite ejecutar el controlador una vez y solo una vez. La takedeclaración al principio hace que la función se detenga . Solo una vez que nuestra aplicación detecte la función, CHANGE_PASSWORD_FORM_FOCUSEDse reactivará y ejecutará el resto de la función.

Un enfoque alternativo sería incluir una bandera en el estado global de su aplicación que pueda voltear una vez que haya ocurrido la primera vez. Pero si desea evitar agregar propiedades al estado, este enfoque funciona bien.

Esperando la primera acción terminada conrace

A veces queremos escuchar varias acciones y tomar pasos específicos dependiendo de cuál de esas acciones terminó primero. Por ejemplo, cuando un usuario envía el pago de un pedido, la transacción puede completarse correctamente o fallar por varios motivos. Para este escenario, recurrimos al racecombinador de efectos. Redux Saga se refiere a racey allcomo combinadores de efectos porque ambos aceptan 1 o más efectos y los manejan simultáneamente.

Veamos cómo 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
  }
}

Este es un enfoque útil para manejar solicitudes asincrónicas. Usamos carrera para los siguientes escenarios:

  • cargar e inicializar SDK y bibliotecas
  • obteniendo segmentos de información de usuario
  • manejo de redireccionamientos
  • validación de búsqueda
  • gestión de fichas
  • Eventos de interfaz de usuario como el siguiente:
  • const { closed } = yield race({
      confirmed: take(AGE_VERIFICATION_MODAL_CONFIRMED),
      closed: take(AGE_VERIFICATION_MODAL_CLOSED)
    })
    

Si queremos tener acceso a ciertos servicios (o contextos) que se pueden compartir a lo largo de nuestras sagas, podemos utilizar getContext. Con una configuración rápida, podemos tener acceso fácilmente a métodos que pueden llamar API, LocalStorage, mecanismos de registro y 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)

Una vez hecho esto, podemos acceder a estos contextos en nuestras sagas de segmentos con 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 })
  }
}

Conclusión

Estos patrones ingeniosos sirven como enfoques confiables para manejar un entorno dinámico como el estado global de una aplicación web masiva. Quizás en una publicación futura, documentaré algunos patrones más que usamos en Just Eat Takeaway. ¡Déjame saber si te gustaría leer sobre eso!