Pregunta ¿Cómo enviar una acción de Redux con un tiempo de espera excedido?


Tengo una acción que actualiza el estado de notificación de mi aplicación. Por lo general, esta notificación será un error o información de algún tipo. Necesito enviar otra acción después de 5 segundos que devolverá el estado de notificación a la inicial, por lo que no hay notificación. La razón principal detrás de esto es proporcionar funcionalidad donde las notificaciones desaparecen automáticamente después de 5 segundos.

No tuve suerte con el uso setTimeout y devolver otra acción y no puede encontrar cómo se hace en línea. Entonces cualquier consejo es bienvenido.


628
2018-02-15 14:03


origen


Respuestas:


No caigas en el trampa de pensar que una biblioteca debería prescribir cómo hacer todo. Si desea hacer algo con un tiempo de espera en JavaScript, debe usar setTimeout. No hay ninguna razón por la cual las acciones de Redux deberían ser diferentes.

Redux hace ofrece algunas formas alternativas de manejar cosas asincrónicas, pero solo debes utilizarlas cuando te das cuenta de que estás repitiendo demasiado código. A menos que tenga este problema, use lo que ofrece el lenguaje e intente con la solución más simple.

Escribir código asíncrono en línea

Esta es de lejos la forma más simple. Y no hay nada específico para Redux aquí.

store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
  store.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)

Del mismo modo, desde dentro de un componente conectado:

this.props.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
  this.props.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)

La única diferencia es que en un componente conectado, por lo general, no tiene acceso a la tienda en sí, sino que obtiene dispatch() o creadores de acciones específicas inyectados como accesorios. Sin embargo, esto no hace ninguna diferencia para nosotros.

Si no le gusta realizar errores tipográficos al distribuir las mismas acciones desde diferentes componentes, es posible que desee extraer creadores de acciones en lugar de distribuir objetos de acción en línea:

// actions.js
export function showNotification(text) {
  return { type: 'SHOW_NOTIFICATION', text }
}
export function hideNotification() {
  return { type: 'HIDE_NOTIFICATION' }
}

// component.js
import { showNotification, hideNotification } from '../actions'

this.props.dispatch(showNotification('You just logged in.'))
setTimeout(() => {
  this.props.dispatch(hideNotification())
}, 5000)

O bien, si los ha vinculado previamente con connect():

this.props.showNotification('You just logged in.')
setTimeout(() => {
  this.props.hideNotification()
}, 5000)

Hasta ahora no hemos usado ningún middleware u otro concepto avanzado.

Extrayendo el creador de acciones asíncronas

El enfoque anterior funciona bien en casos simples, pero es posible que tenga algunos problemas:

  • Te obliga a duplicar esta lógica en cualquier lugar donde quieras mostrar una notificación.
  • Las notificaciones no tienen ID, por lo que tendrás una condición de carrera si muestras dos notificaciones lo suficientemente rápido. Cuando termine el primer tiempo de espera, se enviará HIDE_NOTIFICATION, ocultando erróneamente la segunda notificación antes que después del tiempo de espera.

Para resolver estos problemas, necesitarás extraer una función que centralice la lógica de tiempo de espera y despache esas dos acciones. Puede verse así:

// actions.js
function showNotification(id, text) {
  return { type: 'SHOW_NOTIFICATION', id, text }
}
function hideNotification(id) {
  return { type: 'HIDE_NOTIFICATION', id }
}

let nextNotificationId = 0
export function showNotificationWithTimeout(dispatch, text) {
  // Assigning IDs to notifications lets reducer ignore HIDE_NOTIFICATION
  // for the notification that is not currently visible.
  // Alternatively, we could store the interval ID and call
  // clearInterval(), but we’d still want to do it in a single place.
  const id = nextNotificationId++
  dispatch(showNotification(id, text))

  setTimeout(() => {
    dispatch(hideNotification(id))
  }, 5000)
}

Ahora los componentes pueden usar showNotificationWithTimeout sin duplicar esta lógica o tener condiciones de carrera con diferentes notificaciones:

// component.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')

// otherComponent.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged out.')    

Por que showNotificationWithTimeout() aceptar dispatch como el primer argumento? Porque necesita enviar acciones a la tienda. Normalmente, un componente tiene acceso a dispatch pero dado que queremos que una función externa controle el despacho, debemos darle control sobre el despacho.

Si tuviera una tienda singleton exportada desde algún módulo, podría importarla y dispatch directamente en él en su lugar:

// store.js
export default createStore(reducer)

// actions.js
import store from './store'

// ...

let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
  const id = nextNotificationId++
  store.dispatch(showNotification(id, text))

  setTimeout(() => {
    store.dispatch(hideNotification(id))
  }, 5000)
}

// component.js
showNotificationWithTimeout('You just logged in.')

// otherComponent.js
showNotificationWithTimeout('You just logged out.')    

Esto parece más simple, pero no recomendamos este enfoque. La razón principal por la que no nos gusta es porque obliga a la tienda a ser un singleton. Esto hace que sea muy difícil de implementar representación del servidor. En el servidor, deseará que cada solicitud tenga su propia tienda, de modo que diferentes usuarios obtengan diferentes datos precargados.

Una tienda singleton también hace que las pruebas sean más difíciles. Ya no puede burlarse de una tienda cuando prueba a los creadores de acciones porque hacen referencia a una tienda real específica exportada desde un módulo específico. Ni siquiera puedes restablecer su estado desde afuera.

Entonces, aunque técnicamente puede exportar una tienda singleton desde un módulo, lo desalentamos. No haga esto a menos que esté seguro de que su aplicación nunca agregará la representación del servidor.

Volviendo a la versión anterior:

// actions.js

// ...

let nextNotificationId = 0
export function showNotificationWithTimeout(dispatch, text) {
  const id = nextNotificationId++
  dispatch(showNotification(id, text))

  setTimeout(() => {
    dispatch(hideNotification(id))
  }, 5000)
}

// component.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')

// otherComponent.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged out.')    

Esto resuelve los problemas con la duplicación de la lógica y nos salva de las condiciones de carrera.

Thunk Middleware

Para aplicaciones simples, el enfoque debería ser suficiente. No se preocupe por el middleware si está contento con él.

Sin embargo, en aplicaciones más grandes, puede encontrar ciertos inconvenientes a su alrededor.

Por ejemplo, parece desafortunado que tengamos que pasar dispatch alrededor. Esto hace que sea más complicado contenedor separado y componentes de presentación porque cualquier componente que distribuye acciones de Redux de forma asíncrona en la forma anterior tiene que aceptar dispatch como un apoyo para que pueda pasarlo más lejos. No puedes simplemente vincular a los creadores de acción con connect() más porque showNotificationWithTimeout() no es realmente un creador de acciones No devuelve una acción de Redux.

Además, puede ser incómodo recordar qué funciones son creadores de acciones sincrónicas como showNotification() y que son ayudantes asincrónicos como showNotificationWithTimeout(). Debe usarlos de manera diferente y tener cuidado de no confundirlos entre sí.

Esta fue la motivación para encontrar una manera de "legitimar" este patrón de proporcionar dispatch a una función auxiliar, y ayudar a Redux a "ver" tales creadores de acciones asincrónicas como un caso especial de los creadores de acciones normales en lugar de funciones totalmente diferentes.

Si todavía está con nosotros y también reconoce que hay un problema en su aplicación, puede usar el Redux Thunk middleware.

En esencia, Redux Thunk le enseña a Redux a reconocer tipos especiales de acciones que de hecho son funciones:

import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'

const store = createStore(
  reducer,
  applyMiddleware(thunk)
)

// It still recognizes plain object actions
store.dispatch({ type: 'INCREMENT' })

// But with thunk middleware, it also recognizes functions
store.dispatch(function (dispatch) {
  // ... which themselves may dispatch many times
  dispatch({ type: 'INCREMENT' })
  dispatch({ type: 'INCREMENT' })
  dispatch({ type: 'INCREMENT' })

  setTimeout(() => {
    // ... even asynchronously!
    dispatch({ type: 'DECREMENT' })
  }, 1000)
})

Cuando este middleware está habilitado, si despachas una función, El middleware de Redux Thunk lo dará dispatch como un argumento También "tragará" tales acciones, así que no se preocupe porque sus reductores reciban argumentos de funciones extrañas. Sus reductores solo recibirán acciones simples de objeto, ya sea emitidas directamente o emitidas por las funciones que acabamos de describir.

Esto no parece muy útil, ¿verdad? No en esta situación particular. Sin embargo, nos permite declarar showNotificationWithTimeout() como creador de acciones de Redux:

// actions.js
function showNotification(id, text) {
  return { type: 'SHOW_NOTIFICATION', id, text }
}
function hideNotification(id) {
  return { type: 'HIDE_NOTIFICATION', id }
}

let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
  return function (dispatch) {
    const id = nextNotificationId++
    dispatch(showNotification(id, text))

    setTimeout(() => {
      dispatch(hideNotification(id))
    }, 5000)
  }
}

Tenga en cuenta que la función es casi idéntica a la que escribimos en la sección anterior. Sin embargo, no acepta dispatchcomo el primer argumento. En cambio, devoluciones una función que acepta dispatch como el primer argumento.

¿Cómo lo usaríamos en nuestro componente? Definitivamente, podríamos escribir esto:

// component.js
showNotificationWithTimeout('You just logged in.')(this.props.dispatch)

Estamos llamando al creador de acciones asíncronas para obtener la función interna que solo desea dispatchy luego pasamos dispatch.

¡Sin embargo, esto es aún más incómodo que la versión original! ¿Por qué fuimos así?

Por lo que te dije antes. Si el middleware de Redux Thunk está habilitado, cada vez que intente despachar una función en lugar de un objeto de acción, el middleware llamará a esa función con dispatch método en sí mismo como el primer argumento.

Entonces podemos hacer esto:

// component.js
this.props.dispatch(showNotificationWithTimeout('You just logged in.'))

Finalmente, el envío de una acción asíncrona (en realidad, una serie de acciones) no se ve diferente de enviar una sola acción de forma síncrona al componente. Lo cual es bueno porque a los componentes no les debería importar si algo sucede de manera síncrona o asíncrona. Simplemente lo abstraccionamos.

Tenga en cuenta que desde que "enseñamos" a Redux a reconocer a esos creadores de acciones "especiales" (los llamamos thunk creadores de acciones), ahora podemos usarlos en cualquier lugar donde usemos creadores de acciones regulares. Por ejemplo, podemos usarlos con connect():

// actions.js

function showNotification(id, text) {
  return { type: 'SHOW_NOTIFICATION', id, text }
}
function hideNotification(id) {
  return { type: 'HIDE_NOTIFICATION', id }
}

let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
  return function (dispatch) {
    const id = nextNotificationId++
    dispatch(showNotification(id, text))

    setTimeout(() => {
      dispatch(hideNotification(id))
    }, 5000)
  }
}

// component.js

import { connect } from 'react-redux'

// ...

this.props.showNotificationWithTimeout('You just logged in.')

// ...

export default connect(
  mapStateToProps,
  { showNotificationWithTimeout }
)(MyComponent)

Estado de Lectura en Thunks

Por lo general, sus reductores contienen la lógica comercial para determinar el próximo estado. Sin embargo, los reductores solo se activan una vez que se despachan las acciones. ¿Qué sucede si tiene un efecto secundario (como llamar a una API) en un creador de acción de procesador de segundo plano y desea evitarlo bajo alguna condición?

Sin usar el middleware thunk, simplemente haría esta comprobación dentro del componente:

// component.js
if (this.props.areNotificationsEnabled) {
  showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')
}

Sin embargo, el objetivo de extraer un creador de acciones era centralizar esta lógica repetitiva en muchos componentes. Afortunadamente, Redux Thunk te ofrece una forma de leer el estado actual de la tienda de Redux. Además de dispatch, también pasa getState como el segundo argumento para la función que devuelve desde su creador de acción thunk. Esto permite que el procesador lea el estado actual de la tienda.

let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
  return function (dispatch, getState) {
    // Unlike in a regular action creator, we can exit early in a thunk
    // Redux doesn’t care about its return value (or lack of it)
    if (!getState().areNotificationsEnabled) {
      return
    }

    const id = nextNotificationId++
    dispatch(showNotification(id, text))

    setTimeout(() => {
      dispatch(hideNotification(id))
    }, 5000)
  }
}

No abuse de este patrón. Es bueno para rescatar llamadas API cuando hay datos almacenados en caché disponibles, pero no es una base muy buena para construir su lógica comercial. Si utiliza getState() solo para despachar condicionalmente diferentes acciones, considere poner la lógica comercial en los reductores en su lugar.

Próximos pasos

Ahora que tienes una intuición básica sobre cómo funcionan los thunk, echa un vistazo a Redux ejemplo asincrónico que los usa.

Puede encontrar muchos ejemplos en los cuales los thunk devuelven Promises. Esto no es obligatorio, pero puede ser muy conveniente. A Redux no le importa lo que regrese de un procesador de segundo plano, pero le da su valor de retorno de dispatch(). Esta es la razón por la que puede devolver una Promesa desde un procesador y esperar que se complete llamando dispatch(someThunkReturningPromise()).then(...).

También puede dividir creadores de acción thunk complejos en varios creadores de acción thunk más pequeños. los dispatch El método proporcionado por thunks puede aceptar thunk en sí mismo, por lo que puede aplicar el patrón recursivamente. De nuevo, esto funciona mejor con Promises porque puede implementar un flujo de control asincrónico además de eso.

Para algunas aplicaciones, puede encontrarse en una situación en la que los requisitos de flujo de control asincrónico son demasiado complejos para expresarse con los procesos. Por ejemplo, reintentar solicitudes fallidas, flujo de reautorización con tokens o una incorporación paso a paso puede ser demasiado prolijo y propenso a errores cuando se lo escribe de esta manera. En este caso, es posible que desee ver soluciones de flujo de control asíncronas más avanzadas, como Redux Saga o Redux Loop. Evalúelos, compare los ejemplos relevantes a sus necesidades y escoja el que más le guste.

Por último, no use nada (incluidos los thunks) si no los necesita de manera genuina. Recuerde que, dependiendo de los requisitos, su solución puede verse tan simple como

store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
  store.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)

No te preocupes a menos que sepas por qué estás haciendo esto.


1969
2018-02-15 17:33



Usando Redux-saga

Como dijo Dan Abramov, si desea un control más avanzado sobre su código asíncrono, puede echarle un vistazo a redux-saga.

Esta respuesta es un ejemplo simple, si quieres mejores explicaciones sobre por qué redux-saga puede ser útil para tu aplicación, verifica esta otra respuesta.

La idea general es que Redux-saga ofrece un intérprete de generadores ES6 que le permite escribir fácilmente un código asíncrono que se parece al código sincrónico (esta es la razón por la que a menudo encontrará bucles infinitos en Redux-saga). De alguna manera, Redux-saga está construyendo su propio lenguaje directamente dentro de Javascript. Redux-saga puede parecer un poco difícil de aprender al principio, porque necesita una comprensión básica de los generadores, pero también entiende el lenguaje ofrecido por Redux-saga.

Trataré de describir aquí el sistema de notificación que construí sobre redux-saga. Este ejemplo actualmente se ejecuta en producción.

Especificación del sistema de notificación avanzada

  • Puede solicitar que se muestre una notificación
  • Puede solicitar una notificación para ocultar
  • Una notificación no debe mostrarse más de 4 segundos
  • Se pueden mostrar múltiples notificaciones al mismo tiempo
  • No se pueden mostrar más de 3 notificaciones al mismo tiempo
  • Si se solicita una notificación mientras ya hay 3 notificaciones visualizadas, entonces colóquela / posponga.

Resultado

Captura de pantalla de mi aplicación de producción Stample.co

toasts

Código

Aquí nombré la notificación toast pero este es un detalle de nombres.

function* toastSaga() {

    // Some config constants
    const MaxToasts = 3;
    const ToastDisplayTime = 4000;


    // Local generator state: you can put this state in Redux store
    // if it's really important to you, in my case it's not really
    let pendingToasts = []; // A queue of toasts waiting to be displayed
    let activeToasts = []; // Toasts currently displayed


    // Trigger the display of a toast for 4 seconds
    function* displayToast(toast) {
        if ( activeToasts.length >= MaxToasts ) {
            throw new Error("can't display more than " + MaxToasts + " at the same time");
        }
        activeToasts = [...activeToasts,toast]; // Add to active toasts
        yield put(events.toastDisplayed(toast)); // Display the toast (put means dispatch)
        yield call(delay,ToastDisplayTime); // Wait 4 seconds
        yield put(events.toastHidden(toast)); // Hide the toast
        activeToasts = _.without(activeToasts,toast); // Remove from active toasts
    }

    // Everytime we receive a toast display request, we put that request in the queue
    function* toastRequestsWatcher() {
        while ( true ) {
            // Take means the saga will block until TOAST_DISPLAY_REQUESTED action is dispatched
            const event = yield take(Names.TOAST_DISPLAY_REQUESTED);
            const newToast = event.data.toastData;
            pendingToasts = [...pendingToasts,newToast];
        }
    }


    // We try to read the queued toasts periodically and display a toast if it's a good time to do so...
    function* toastScheduler() {
        while ( true ) {
            const canDisplayToast = activeToasts.length < MaxToasts && pendingToasts.length > 0;
            if ( canDisplayToast ) {
                // We display the first pending toast of the queue
                const [firstToast,...remainingToasts] = pendingToasts;
                pendingToasts = remainingToasts;
                // Fork means we are creating a subprocess that will handle the display of a single toast
                yield fork(displayToast,firstToast);
                // Add little delay so that 2 concurrent toast requests aren't display at the same time
                yield call(delay,300);
            }
            else {
                yield call(delay,50);
            }
        }
    }

    // This toast saga is a composition of 2 smaller "sub-sagas" (we could also have used fork/spawn effects here, the difference is quite subtile: it depends if you want toastSaga to block)
    yield [
        call(toastRequestsWatcher),
        call(toastScheduler)
    ]
}

Y el reductor:

const reducer = (state = [],event) => {
    switch (event.name) {
        case Names.TOAST_DISPLAYED:
            return [...state,event.data.toastData];
        case Names.TOAST_HIDDEN:
            return _.without(state,event.data.toastData);
        default:
            return state;
    }
};

Uso

Usted puede simplemente enviar TOAST_DISPLAY_REQUESTED eventos. Si envía 4 solicitudes, solo se mostrarán 3 notificaciones, y la 4 aparecerá más tarde una vez que desaparezca la primera notificación.

Tenga en cuenta que no recomiendo específicamente el despacho TOAST_DISPLAY_REQUESTED de JSX. Prefieres agregar otra saga que escuche tus eventos de aplicaciones ya existentes y luego enviar el TOAST_DISPLAY_REQUESTED: su componente que desencadena la notificación, no tiene que estar estrechamente vinculado al sistema de notificación.

Conclusión

Mi código no es perfecto, pero se ejecuta en producción con 0 errores durante meses. Redux-saga y los generadores son un poco difíciles inicialmente, pero una vez que los entiendas, este tipo de sistema es bastante fácil de construir.

Incluso es bastante fácil implementar reglas más complejas, como:

  • cuando demasiadas notificaciones están "en cola", conceden menos tiempo de visualización para cada notificación, de modo que el tamaño de la cola puede disminuir más rápidamente.
  • detectar cambios en el tamaño de la ventana y cambiar el número máximo de notificaciones visualizadas en consecuencia (por ejemplo, escritorio = 3, retrato del teléfono = 2, paisaje del teléfono = 1)

Honestidad, buena suerte implementando este tipo de cosas correctamente con thunks.

Tenga en cuenta que puede hacer exactamente el mismo tipo de cosas con redux-observable que es muy similar a redux-saga. Es casi lo mismo y es una cuestión de gusto entre generadores y RxJS.


142
2017-07-25 17:44



Puedes hacer esto con redux-thunk. Hay un guía en documento redux para acciones asincrónicas como setTimeout.


18
2018-02-15 14:16



Yo recomendaría también echar un vistazo a la Patrón SAM.

El patrón SAM aboga por incluir un "predicado de próxima acción" donde las acciones (automáticas) como "las notificaciones desaparecen automáticamente después de 5 segundos" se activan una vez que el modelo se ha actualizado (modelo SAM ~ estado reductor + almacenamiento).

El patrón aboga por secuenciar acciones y mutaciones de modelo de a una por vez, porque el "estado de control" del modelo "controla" qué acciones son habilitadas y / o ejecutadas automáticamente por el predicado de la próxima acción. Simplemente no puede predecir (en general) qué estado tendrá el sistema antes de procesar una acción y, por lo tanto, si su próxima acción esperada será permitida / posible.

Entonces, por ejemplo, el código,

export function showNotificationWithTimeout(dispatch, text) {
  const id = nextNotificationId++
  dispatch(showNotification(id, text))

  setTimeout(() => {
    dispatch(hideNotification(id))
  }, 5000)
}

no se permitiría con SAM, porque el hecho de que una acción hideNotification pueda ser despachada depende de que el modelo acepte exitosamente el valor "showNotication: true". Puede haber otras partes del modelo que le impidan aceptarlo y, por lo tanto, no habría ninguna razón para activar la acción hideNotification.

Recomiendo encarecidamente que se implemente un predicado apropiado de próxima acción después de las actualizaciones de la tienda y se conozca el nuevo estado de control del modelo. Esa es la forma más segura de implementar el comportamiento que estás buscando.

Puedes unirte a nosotros en Gitter si lo deseas. También hay una Guía de inicio de SAM disponible aquí.


17
2018-02-24 02:31



Un repositorio con proyectos de muestra

Actual hay cuatro proyectos de muestra:

  1. Escribir código asíncrono en línea
  2. Extrayendo el creador de acciones asíncronas
  3. Utilice Redux Thunk
  4. Usa Redux Saga

La respuesta aceptada es asombrosa.

Pero falta algo:

  1. No hay proyectos de muestra ejecutables, solo algunos fragmentos de código.
  2. No hay código de muestra para otras alternativas, como:
    1. Redux Saga

Así que creé el Hola, Async repositorio para agregar las cosas faltantes:

  1. Proyectos ejecutables. Puede descargarlos y ejecutarlos sin modificaciones.
  2. Proporcione código de muestra para más alternativas:

Redux Saga

La respuesta aceptada ya proporciona fragmentos de código de muestra para Async Code Inline, Async Action Generator y Redux Thunk. En aras de la exhaustividad, proporciono fragmentos de código para Redux Saga:

// actions.js

export const showNotification = (id, text) => {
  return { type: 'SHOW_NOTIFICATION', id, text }
}

export const hideNotification = (id) => {
  return { type: 'HIDE_NOTIFICATION', id }
}

export const showNotificationWithTimeout = (text) => {
  return { type: 'SHOW_NOTIFICATION_WITH_TIMEOUT', text }
}

Las acciones son simples y puras.

// component.js

import { connect } from 'react-redux'

// ...

this.props.showNotificationWithTimeout('You just logged in.')

// ...

export default connect(
  mapStateToProps,
  { showNotificationWithTimeout }
)(MyComponent)

Nada es especial con el componente.

// sagas.js

import { takeEvery, delay } from 'redux-saga'
import { put } from 'redux-saga/effects'
import { showNotification, hideNotification } from './actions'

// Worker saga
let nextNotificationId = 0
function* showNotificationWithTimeout (action) {
  const id = nextNotificationId++
  yield put(showNotification(id, action.text))
  yield delay(5000)
  yield put(hideNotification(id))
}

// Watcher saga, will invoke worker saga above upon action 'SHOW_NOTIFICATION_WITH_TIMEOUT'
function* notificationSaga () {
  yield takeEvery('SHOW_NOTIFICATION_WITH_TIMEOUT', showNotificationWithTimeout)
}

export default notificationSaga

Las sagas se basan en Generadores ES6

// index.js

import createSagaMiddleware from 'redux-saga'
import saga from './sagas'

const sagaMiddleware = createSagaMiddleware()

const store = createStore(
  reducer,
  applyMiddleware(sagaMiddleware)
)

sagaMiddleware.run(saga)

Comparado con Redux Thunk

Pros

  • No terminas en el infierno de devolución de llamada.
  • Puede probar sus flujos asincrónicos fácilmente.
  • Tus acciones se mantienen puras.

Contras

  • Depende de los generadores ES6, que es relativamente nuevo.

por favor refiérase a proyecto ejecutable si los fragmentos de código anteriores no responden a todas sus preguntas.


16
2017-12-24 12:53



Después de probar los diversos enfoques populares (creadores de acción, thunks, sagas, épicas, efectos, middleware personalizado), todavía sentía que tal vez había margen de mejora, así que documenté mi viaje en este artículo de blog, ¿Dónde pongo mi lógica comercial en una aplicación React / Redux? 

Al igual que las discusiones aquí, traté de contrastar y comparar los diversos enfoques. Eventualmente me llevó a presentar una nueva biblioteca redux-logic que toma inspiración de epopeyas, sagas, middleware personalizado.

Le permite interceptar acciones para validar, verificar, autorizar y proporcionar una forma de realizar async IO.

Algunas funcionalidades comunes simplemente pueden declararse como antirrebote, estrangulamiento, cancelación y solo utilizando la respuesta de la última solicitud (takeLatest). redux-logic envuelve su código brindando esta funcionalidad para usted.

Eso te libera para implementar tu lógica de negocio principal como quieras. No tiene que usar observables o generadores a menos que lo desee. Utilice funciones y devoluciones de llamada, promesas, funciones asíncronas (async / await), etc.

El código para hacer una notificación simple de 5s sería algo así como:

const notificationHide = createLogic({
  // the action type that will trigger this logic
  type: 'NOTIFICATION_DISPLAY',
  
  // your business logic can be applied in several
  // execution hooks: validate, transform, process
  // We are defining our code in the process hook below
  // so it runs after the action hit reducers, hide 5s later
  process({ getState, action }, dispatch) {
    setTimeout(() => {
      dispatch({ type: 'NOTIFICATION_CLEAR' });
    }, 5000);
  }
});
    

Tengo un ejemplo de notificación más avanzado en mi repositorio que funciona de manera similar a lo que Sebastian Lorber describió en el que puede limitar la visualización a N elementos y rotar a través de cualquiera que esté en cola. ejemplo de notificación de reducción de lógica 

Tengo una variedad de Ejemplos de jsfiddle de redux-logic así como ejemplos completos. Continúo trabajando en documentos y ejemplos.

Me encantaría escuchar tus comentarios.


13
2017-08-25 01:15



Si desea tiempo de espera en acciones selectivas, puede intentar middleware enfoque. Me enfrenté a un problema similar para manejar acciones basadas en promesas de forma selectiva y esta solución fue más flexible.

Digamos que tu creador de acciones se ve así:

//action creator
buildAction = (actionData) => ({
    ...actionData,
    timeout: 500
})

el tiempo de espera puede contener múltiples valores en la acción anterior

  • número en ms - para una duración específica de tiempo de espera
  • true - para una duración de tiempo de espera constante. (manejado en el middleware)
  • undefined - para envío inmediato

Su implementación de middleware se vería así:

//timeoutMiddleware.js
const timeoutMiddleware = store => next => action => {

  //If your action doesn't have any timeout attribute, fallback to the default handler
  if(!action.timeout) {
    return next (action)
  }

  const defaultTimeoutDuration = 1000;
  const timeoutDuration = Number.isInteger(action.timeout) ? action.timeout || defaultTimeoutDuration;

//timeout here is called based on the duration defined in the action.
  setTimeout(() => {
    next (action)
  }, timeoutDuration)
}

Ahora puede enrutar todas sus acciones a través de esta capa de middleware usando redux.

createStore(reducer, applyMiddleware(timeoutMiddleware))

Puedes encontrar algunos ejemplos similares aquí


5
2017-09-15 13:24