Comment envoyer une action Redux avec un timeout?

j'ai une action qui met à jour l'état de notification de ma demande. Généralement, cette notification sera une erreur ou une info d'une certaine sorte. Je dois ensuite envoyer une autre action après 5 secondes qui retournera l'état de notification à l'état initial, donc aucune notification. La raison principale derrière ceci est de fournir des fonctionnalités où les notifications disparaissent automatiquement après 5 secondes.

Je n'ai pas eu de chance en utilisant setTimeout et en retournant une autre action et ne trouve pas comment cela est fait en ligne. Donc, tous les conseils sont les bienvenus.

673
demandé sur qbolec 2016-02-15 17:03:59

13 réponses

Ne pas tomber dans le piège de penser une bibliothèque devrait prescrire la façon de faire tout ce qui . Si vous voulez faire quelque chose avec un délai dans JavaScript, vous devez utiliser setTimeout . Il n'y a aucune raison pour que les actions de Redux soient différentes.

Redux does offrent quelques façons alternatives de traiter les choses asynchrones, mais vous ne devriez utiliser ceux-ci que lorsque vous vous rendez compte que vous répétez trop de code. À moins que vous n'ayez ce problème, utilisez ce que la langue offre et optez pour la solution la plus simple.

Écriture Asynchrone Code Inline

C'est de loin le moyen le plus simple. Et il n'y a rien de spécifique à Redux ici.

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

de même, de l'intérieur d'un composant connecté:

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

la seule différence est que dans un composant connecté vous n'avez généralement pas accès au magasin lui-même, mais obtenez soit dispatch() ou des créateurs d'action spécifiques injectés comme accessoires. Cependant, cela ne fait aucune différence pour nous.

si vous n'aimez pas faire des fautes de frappe lors de l'envoi des mêmes actions à partir de différents composants, vous pourriez vouloir extraire des créateurs d'action au lieu d'envoyer des objets d'action en ligne:

// 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)

ou, si vous les avez déjà liés avec connect() :

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

jusqu'à présent, nous n'avons pas utilisé tout middleware ou autre concept avancé.

Extraction D'Action Asynchrone Créateur

l'approche ci-dessus fonctionne bien dans les cas simples, mais vous pourriez trouver qu'il a quelques problèmes:

  • il vous oblige à dupliquer cette logique n'importe où vous voulez montrer une notification.
  • les notifications n'ont pas D'IDs donc vous aurez une condition de course si vous montrez deux notifications assez rapidement. Lorsque l' le premier temps d'arrêt se termine, il expédiera HIDE_NOTIFICATION , masquant par erreur la deuxième notification plus tôt qu'après le temps d'arrêt.

pour résoudre ces problèmes, vous auriez besoin d'extraire une fonction qui centralise la logique de temporisation et envoie ces deux actions. Il pourrait ressembler à ceci:

// 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)
}

maintenant les composants peuvent utiliser showNotificationWithTimeout sans dupliquer cette logique ou avoir des conditions de course avec différentes notifications:

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

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

pourquoi showNotificationWithTimeout() accepte dispatch comme premier argument? Parce qu'il doit envoyer des actions au magasin. Normalement, un composant a accès à dispatch mais comme nous voulons qu'une fonction externe prenne le contrôle de l'expédition, nous devons lui donner le contrôle de l'expédition.

si vous aviez un magasin singleton exporté à partir d'un module, vous pourriez juste l'importer et dispatch directement sur elle à la place:

// 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.')    

cela semble plus simple, mais nous ne recommandons pas cette approche . La principale raison pour laquelle nous ne l'aimons pas est parce que il oblige store à être un singleton . Cela rend très difficile la mise en œuvre de rendant le serveur . Sur le serveur, vous voudrez que chaque requête ait sa propre mémoire, de sorte que les différents utilisateurs obtiennent des données préchargées différentes.

Un singleton magasin fait aussi des tests plus difficile. Vous ne pouvez plus vous moquer d'un magasin lorsque vous testez les créateurs d'action parce qu'ils font référence à un magasin réel spécifique exporté à partir d'un module spécifique. Vous ne pouvez même pas réinitialiser l'état de l'extérieur.

donc même si vous pouvez techniquement exporter un magasin singleton à partir d'un module, nous le décourageons. Ne faites pas cela à moins que vous ne soyez sûr que votre application n'ajoutera jamais le rendu du serveur.

retour à la version précédente:

// 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.')    

This résout les problèmes de duplication de la logique et nous sauve des conditions de course.

Thunk Middleware

pour les applications simples, l'approche devrait suffire. Ne vous inquiétez pas pour middleware si vous êtes heureux avec elle.

dans les grandes applications, cependant, vous pourriez trouver certains inconvénients autour de lui.

par exemple, il semble malheureux que nous devions passer dispatch autour. Cela rend plus difficile pour conteneur séparé et Composants de présentation parce que tout composant qui envoie Redux actions asynchrones dans la manière ci-dessus doit accepter dispatch comme un accessoire pour qu'il puisse passer plus loin. Vous ne pouvez pas seulement lier les créateurs d'action avec connect() plus parce que showNotificationWithTimeout() n'est pas vraiment un créateur d'action. Il ne renvoie pas un Redux de l'action.

de plus, il peut être difficile de se rappeler quelles fonctions sont synchrones. des créateurs comme showNotification() et qui sont des aides asynchrones comme showNotificationWithTimeout() . Vous devez les utiliser différemment et faire attention à ne pas les confondre.

C'était la motivation pour trouver un moyen de" légitimer "ce modèle de fournir dispatch à une fonction d'AIDE, et aider Redux" voir "tels créateurs d'action asynchrone comme un cas spécial de créateurs d'action normale plutôt que des fonctions totalement différentes.

si vous êtes toujours avec nous et que vous reconnaissez aussi comme un problème dans votre application, vous êtes invités à utiliser le Redux Thunk middleware.

dans un gist, Redux Thunk enseigne à Redux de reconnaître les types spéciaux d'actions qui sont en fait des fonctions:

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)
})

lorsque cet middleware est activé, si vous envoyez une fonction , Redux Thunk middleware vous donnera dispatch comme argument. Il va également "avaler" de telles actions donc ne vous inquiétez pas de vos réducteurs recevant des arguments de fonction bizarres. Vos réducteurs ne recevront que des actions d'objets simples-soit émises directement, soit émises par les fonctions comme nous venons de le décrire.

cela ne semble pas très utile, n'est-ce pas? Pas dans cette situation particulière. Cependant, il nous permet de déclarer showNotificationWithTimeout() comme un créateur D'action régulier 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)
  }
}

noter comment la fonction est presque identique à celui que nous avons écrit dans la section précédente. Cependant, il n'accepte pas dispatch comme premier argument. Au lieu de cela, il retourne une fonction qui accepte dispatch comme premier argument.

comment l'utiliserions-nous dans notre composant? Certainement, nous pourrions écrire ceci:

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

nous appelons le créateur d'action async pour obtenir la fonction intérieure qui veut juste dispatch , et puis nous passons dispatch .

cependant c'est encore plus embarrassant que la version originale! Pourquoi avons-nous même y aller?

à cause de ce que je vous ai dit. si Redux Thunk middleware est activé, chaque fois que vous tentez d'envoyer une fonction au lieu d'un objet d'action, le middleware appellera cette fonction avec la méthode dispatch elle-même comme premier argument .

donc on peut faire ça à la place:

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

enfin, l'envoi d'une action asynchrone (en fait, une série d'actions) n'est pas différent de l'envoi d'une seule action synchrone au composant. Ce qui est bien parce que les composants ne devraient pas se soucier de savoir si quelque chose se passe de façon synchrone ou asynchrone. Nous avons seulement fait une abstraction de tout ça.

notez que depuis que nous avons" enseigné " à Redux de reconnaître ces créateurs d'action "spéciaux" (nous les appelons thunk action creators), nous pouvons maintenant les utiliser dans n'importe quel endroit où nous utiliserions des créateurs d'action réguliers. Par exemple, nous pouvons les utiliser avec 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)

la Lecture de l'État dans les Thunks

habituellement, vos réducteurs contiennent la logique commerciale pour déterminer l'état suivant. Cependant, les réducteurs ne se déclenchent qu'après l'envoi des actions. Que faire si vous avez un effet secondaire (comme appeler une API) dans un créateur d'action thunk, et que vous voulez l'empêcher sous certaines conditions?

sans utiliser le thunk middleware, vous feriez juste cette vérification à l'intérieur du composant:

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

cependant, le but de l'extraction d'un créateur d'action était de centraliser cette logique répétitive à travers de nombreux composants. Heureusement, Redux Thunk vous offre un moyen de lire l'état actuel du magasin Redux. En plus de dispatch , il passe aussi getState comme deuxième argument la fonction que vous revenez de votre créateur d'action thunk. Cela permet au thunk de lire l'état actuel du magasin.

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)
  }
}

n'abusez pas de ce schéma. Il est bon pour renflouer les appels API quand il ya des données mises en cache disponibles, mais ce n'est pas une très bonne base pour construire votre logique d'affaires sur. Si vous utilisez getState() seulement pour expédier conditionnellement différentes actions, envisager de mettre la logique commerciale dans les réducteurs à la place.

Prochaines Étapes

maintenant que vous avez une intuition de base sur la façon dont les malles fonctionnent, consultez Redux async exemple qui les utilise.

vous pouvez trouver de nombreux exemples dans lesquels les malfrats rendent des promesses. Ce n'est pas nécessaire mais peut être très pratique. Redux ne se soucie pas de ce que vous revenez d'un thunk, mais il vous donne sa valeur de retour de dispatch() . C'est pourquoi vous pouvez retourner une promesse d'un tonneau et attendre complet en appelant dispatch(someThunkReturningPromise()).then(...) .

vous pouvez également diviser les créateurs d'action thunk complexes en plusieurs créateurs d'action thunk plus petits. La méthode dispatch fournie par thunks peut accepter les thunks elle-même, de sorte que vous pouvez appliquer le modèle de façon récursive. Encore une fois, cela fonctionne mieux avec les promesses parce que vous pouvez mettre en œuvre le flux de contrôle asynchrone en plus de cela.

Pour certaines applications, vous pouvez vous trouver dans une situation où votre flux de contrôle asynchrone les exigences sont trop complexes pour être exprimées avec des gros mots. Par exemple, le fait de retracer des requêtes qui ont échoué, le flux de reauthorization avec des tokens, ou un onboarding étape par étape peut être trop verbeux et sujet à des erreurs lorsqu'il est écrit de cette façon. Dans ce cas, vous voudrez peut-être examiner des solutions de contrôle asynchrones plus avancées telles que Redux Saga ou Redux Loop . Évaluez-les, comparez les exemples pertinents à vos besoins et choisissez celui qui vous plaît le plus.

enfin, n'utilisez rien (y compris les malles) si vous n'en avez pas vraiment besoin. Rappelez - vous que, selon les exigences, votre solution peut sembler aussi simple que

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

ne t'en fais pas à moins de savoir pourquoi tu fais ça.

2066
répondu Dan Abramov 2016-10-20 02:28:06

à l'Aide de Redux-saga

comme Dan Abramov l'a dit, si vous voulez un contrôle plus avancé de votre code async, vous pouvez jeter un oeil à redux-saga .

cette réponse est un exemple simple, si vous voulez de meilleures explications sur la raison pour laquelle redux-saga peut être utile pour votre application, Cochez cette autre réponse .

L'idée générale est que Redux-saga offre un interpréteur de générateurs ES6 qui vous permet d'écrire facilement du code async qui ressemble à du code synchrone (c'est pourquoi vous trouverez souvent infinite while loops dans Redux-saga). D'une certaine manière, Redux-saga construit son propre langage directement à L'intérieur de Javascript. Redux-saga peut se sentir un peu difficile à apprendre au début, parce que vous avez besoin de compréhension de base des générateurs, mais aussi comprendre le langage offert par Redux-saga.

je vais essayer ici de décrire le système de notification J'ai construit sur le dessus de redux-saga. Cet exemple fonctionne actuellement en production.

Advanced notification system specification""
  • vous pouvez demander qu'une notification soit affichée
  • vous pouvez demander une notification pour cacher
  • Une notification ne doivent pas être affichées à plus de 4 secondes
  • plusieurs notifications peuvent être affichées en même temps
  • pas plus de 3 notifications peuvent être affichées en même temps
  • si une notification est demandée alors qu'il y a déjà 3 notifications affichées, faites la queue/reportez-la.

résultat

Capture d'écran de ma production app Stample.co

toasts

Code

ici j'ai nommé la notification un toast mais c'est un détail d'appellation.

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)
    ]
}

et le réducteur:

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;
    }
};

Utilisation

vous pouvez simplement envoyer TOAST_DISPLAY_REQUESTED événements. Si vous envoyez 4 requêtes, seulement 3 notifications seront affichées, et la 4ème apparaîtra un peu plus tard lorsque la 1ère notification disparaîtra.

notez que je ne recommande pas spécifiquement l'expédition TOAST_DISPLAY_REQUESTED de JSX. Vous préférez plutôt ajouter une autre saga qui écoute vos événements app déjà existants, et ensuite envoyer le TOAST_DISPLAY_REQUESTED : votre composant qui déclenche la notification, n'a pas besoin d'être étroitement couplé au système de notification.

Conclusion

mon code n'est pas parfait mais fonctionne en production avec 0 bogue depuis des mois. Redux-saga et les générateurs sont un peu difficiles au départ mais une fois que vous les comprenez ce genre de système est assez facile à construire.

il est même assez facile de mettre en œuvre des règles plus complexes, comme:

  • lorsque trop de notifications sont "en file d'attente", donner moins de temps d'affichage pour chaque notification afin que la taille de la file d'attente peut diminuer plus rapidement.
  • détecter les changements de taille de fenêtre, et modifier le nombre maximum de notifications affichées en conséquence (par exemple, bureau=3, portrait de téléphone = 2, paysage de téléphone = 1)

Honnestly, bonne chance la mise en œuvre de ce genre de trucs correctement avec les thunks.

Note vous pouvez faire exactement le même genre de chose avec redux-observables , qui est très similaire à redux-saga. C'est presque la même chose et c'est une question de goût entre les générateurs et les RxJS.

153
répondu Sebastien Lorber 2018-05-16 15:10:57

je recommande également de jeter un oeil à la Sam pattern .

le Sam pattern préconise l'inclusion d'un" Next-action-predicate "où des actions (automatiques) telles que" notifications disparaissent automatiquement après 5 secondes " sont déclenchées une fois que le modèle a été mis à jour (SAM model ~ reducer state + store).

le modèle préconise des actions de séquençage et des mutations de modèle un à la fois, parce que le " contrôle indiquer" du modèle "contrôle" quelles actions sont activées et / ou exécutées automatiquement par le prédicat d'action suivant. Vous ne pouvez tout simplement pas prédire (en général) quel sera l'état du système avant de traiter une action et donc si votre prochaine action attendue sera autorisée/possible.

ainsi par exemple le code,

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

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

ne serait pas autorisé avec SAM, parce que le fait qu'une action de hideNotification peut être expédiée dépend de le modèle accepte avec succès la valeur "showNotication: true". Il pourrait y avoir d'autres parties du modèle qui l'empêche de l'accepter et, par conséquent, il n'y aurait pas de raison de déclencher la hideNotification action.

je recommande fortement que la mise en œuvre d'un prédicat de la prochaine action appropriée après les mises à jour de magasin et le nouvel état de contrôle du modèle peut être connu. C'est la façon la plus sûre de mettre en œuvre le comportement que vous recherchez.

You vous pouvez nous rejoindre sur Gitter si vous voulez. Il y a aussi un SAM guide de démarrage disponible ici .

19
répondu Jean-Jacques Dubray 2016-02-24 02:31:26

vous pouvez le faire avec redux-thunk . Il existe un guide dans le document redux pour les actions async comme setTimeout.

19
répondu Fatih Erikli 2016-10-10 13:35:34

Un référentiel avec des exemples de projets

Current il y a quatre exemples de projets:

  1. Écriture Asynchrone Code Inline
  2. Extraction D'Action Asynchrone Créateur
  3. Use Redux Thunk
  4. Use Redux Saga

Les accepté de répondre est génial.

mais il manque quelque chose:

  1. Non praticable projets de l'échantillon, juste quelques extraits de code.
  2. pas de code type pour les autres variantes, telles que:
    1. Redux Saga

J'ai donc créé le référentiel Hello Async pour ajouter le fichier manquant choses:

  1. Praticable projets. Vous pouvez les télécharger et les exécuter sans modification.
  2. fournir un exemple de code pour d'autres alternatives:

Redux Saga

la réponse acceptée fournit déjà des exemples de code snippets pour le Code Async Inline, Async action Generator et Redux Thunk. Par souci d'exhaustivité, je fournis des extraits de code pour 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 }
}

les Actions sont simples et pures.

// component.js

import { connect } from 'react-redux'

// ...

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

// ...

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

Rien de spécial avec le composant.

// 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

Sagas sont basés sur générateurs ES6

// index.js

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

const sagaMiddleware = createSagaMiddleware()

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

sagaMiddleware.run(saga)

comparé à Redux Thunk

Pros

  • vous ne finissez pas en enfer de rappel.
  • vous pouvez tester vos flux asynchrones facilement.
  • vos actions restent pures.

Cons

  • il dépend de générateurs ES6 qui est relativement nouveau.

s'il vous Plaît se référer à la praticable projet si les extraits de code ci-dessus ne sont pas la réponse à tous vos question.

18
répondu Tyler Long 2017-04-23 06:26:26

après avoir essayé les différentes approches populaires (créateurs d'action, thunks, sagas, epics, effets, middleware personnalisé), j'ai encore senti qu'il y avait peut-être place à l'amélioration, donc j'ai documenté mon voyage dans cet article de blog, Où puis-je mettre ma logique d'affaires dans une application React/Redux?

tout comme les discussions ici, j'ai essayé de comparer et comparer les différentes approches. Finalement, il m'a conduit à introduire une nouvelle bibliothèque redux-logic qui s'inspire des épopées, sagas, middleware personnalisé.

il vous permet d'intercepter des actions pour valider, vérifier, autoriser, ainsi que fournir un moyen d'effectuer asynchrone.

certaines fonctionnalités communes peuvent simplement être déclarées comme le débonctionnement, l'étranglement, l'Annulation, et seulement en utilisant la réponse de la dernière requête (takeLatest). redux-logic enveloppe votre code en fournissant cette fonctionnalité pour vous.

qui vous libère pour mettre en œuvre votre logique d'affaires de base comme vous le souhaitez. Vous n'avez pas à utiliser des observables ou des générateurs à moins que vous le souhaitez. Utilisez les fonctions et les callbacks, les promesses, les fonctions async (async/wait), etc.

le code pour faire une simple notification 5s serait quelque chose comme:

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);
  }
});
    

j'ai un exemple de notification plus avancé dans mon repo qui fonctionne similaire à ce que Sebastian Lorber décrit où vous pourriez limiter l'affichage à N articles et tourner à travers tout ce qui a fait la queue. redux-exemple de notification logique

j'ai une variété de redux-logique jsfiddle des exemples vivants ainsi que des exemples complets . Je continue à travailler sur des documents et des exemples.

j'aimerais entendre vos commentaires.

16
répondu Jeff Barczewski 2016-08-25 01:31:52

je comprends que cette question est un peu vieille mais je vais introduire une autre solution en utilisant redux-observable aka. Épique.

citant la documentation officielle:

qu'est-Ce que redux observables?

RxJS 5 middleware pour Redux. Composer et annuler des actions async pour créer des effets secondaires et plus.

une épopée est le noyau primitif de redux-observables.

C'est une fonction qui prend un flux d'actions et renvoie un flux d'actions. Actions, actions.

en plus ou moins de mots, vous pouvez créer une fonction qui reçoit des actions par le biais d'un flux et ensuite retourner un nouveau flux d'actions (en utilisant des effets secondaires courants tels que des temps morts, des retards, des intervalles et des requêtes).

Permettez-moi de poster le code et puis expliquer un peu plus

magasin.js

import {createStore, applyMiddleware} from 'redux'
import {createEpicMiddleware} from 'redux-observable'
import {Observable} from 'rxjs'
const NEW_NOTIFICATION = 'NEW_NOTIFICATION'
const QUIT_NOTIFICATION = 'QUIT_NOTIFICATION'
const NOTIFICATION_TIMEOUT = 2000

const initialState = ''
const rootReducer = (state = initialState, action) => {
  const {type, message} = action
  console.log(type)
  switch(type) {
    case NEW_NOTIFICATION:
      return message
    break
    case QUIT_NOTIFICATION:
      return initialState
    break
  }

  return state
}

const rootEpic = (action$) => {
  const incoming = action$.ofType(NEW_NOTIFICATION)
  const outgoing = incoming.switchMap((action) => {
    return Observable.of(quitNotification())
      .delay(NOTIFICATION_TIMEOUT)
      //.takeUntil(action$.ofType(NEW_NOTIFICATION))
  });

  return outgoing;
}

export function newNotification(message) {
  return ({type: NEW_NOTIFICATION, message})
}
export function quitNotification(message) {
  return ({type: QUIT_NOTIFICATION, message});
}

export const configureStore = () => createStore(
  rootReducer,
  applyMiddleware(createEpicMiddleware(rootEpic))
)

de l'index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import {configureStore} from './store.js'
import {Provider} from 'react-redux'

const store = configureStore()

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

App.js

import React, { Component } from 'react';
import {connect} from 'react-redux'
import {newNotification} from './store.js'

class App extends Component {

  render() {
    return (
      <div className="App">
        {this.props.notificationExistance ? (<p>{this.props.notificationMessage}</p>) : ''}
        <button onClick={this.props.onNotificationRequest}>Click!</button>
      </div>
    );
  }
}

const mapStateToProps = (state) => {
  return {
    notificationExistance : state.length > 0,
    notificationMessage : state
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    onNotificationRequest: () => dispatch(newNotification(new Date().toDateString()))
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(App)

le code clé pour résoudre ce problème est aussi facile que pie comme vous pouvez le voir, la seule chose qui apparaît différent des autres réponses est la fonction rootEpic.

Point 1. Comme avec les sagas, vous devez combiner les épopées afin d'obtenir un haut niveau de fonction qui reçoit un flux d'actions et de renvoie un flux d'actions, de sorte que vous pouvez l'utiliser avec l'usine middleware createEpicMiddleware . Dans notre cas, nous avons seulement besoin d'un si nous avons seulement notre rootEpic donc nous n'avons pas à combiner quoi que ce soit, mais il est bon de savoir fait.

Point 2. Notre rootEpic qui prend soin de la logique des effets secondaires ne prend environ 5 lignes de code qui est impressionnant! Y compris le fait que c'est assez bien déclarative!

Point 3. Line by line rootEpic explication (in comments)

const rootEpic = (action$) => {
  // sets the incoming constant as a stream 
  // of actions with  type NEW_NOTIFICATION
  const incoming = action$.ofType(NEW_NOTIFICATION)
  // Merges the "incoming" stream with the stream resulting for each call
  // This functionality is similar to flatMap (or Promise.all in some way)
  // It creates a new stream with the values of incoming and 
  // the resulting values of the stream generated by the function passed
  // but it stops the merge when incoming gets a new value SO!,
  // in result: no quitNotification action is set in the resulting stream
  // in case there is a new alert
  const outgoing = incoming.switchMap((action) => {
    // creates of observable with the value passed 
    // (a stream with only one node)
    return Observable.of(quitNotification())
      // it waits before sending the nodes 
      // from the Observable.of(...) statement
      .delay(NOTIFICATION_TIMEOUT)
  });
  // we return the resulting stream
  return outgoing;
}

j'espère que cela aide!

7
répondu cnexans 2018-03-11 12:52:21

si vous voulez gérer le temps mort sur les actions sélectives, vous pouvez essayer l'approche middleware . J'ai fait face à un problème similaire pour traiter les actions basées sur la promesse de manière sélective et cette solution était plus souple.

disons que votre créateur d'action ressemble à ceci:

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

délai d'attente peut contenir plusieurs valeurs dans l'action ci-dessus

  • nombre dans ms-pour une durée de temps d'arrêt spécifique
  • vrai - pour une constante de temps. (traitées dans le middleware)
  • Non défini - pour expédition immédiate

votre implémentation de middleware ressemblerait à ceci:

//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)
}

vous pouvez maintenant Router toutes vos actions à travers cette couche middleware en utilisant redux.

createStore(reducer, applyMiddleware(timeoutMiddleware))

vous pouvez trouver quelques exemples similaires ici

5
répondu Yash 2016-09-15 13:24:05

Pourquoi est-ce si difficile? C'est juste de la logique de l'INTERFACE utilisateur. Utiliser une action dédiée pour définir les données de notification:

dispatch({ notificationData: { message: 'message', expire: +new Date() + 5*1000 } })

et un composant dédié pour l'afficher:

const Notifications = ({ notificationData }) => {
    if(notificationData.expire > this.state.currentTime) {
      return <div>{notificationData.message}</div>
    } else return null;
}

dans ce cas, les questions devraient être" comment nettoyez-vous l'ancien état?", "comment notifier un composant que les temps ont changé,"

vous pouvez implémenter une action de TIMEOUT qui est envoyée sur setTimeout à partir d'un composant.

C'est peut-être bien le nettoyer à chaque fois qu'une nouvelle notification est affichée.

de toute façon, il devrait y avoir un peu de setTimeout quelque part, non? Pourquoi ne pas le faire dans un composant

setTimeout(() => this.setState({ currentTime: +new Date()}), 
           this.props.notificationData.expire-(+new Date()) )

la motivation est que la fonctionnalité" notification fade out " est vraiment une préoccupation D'UI. Donc, il simplifie les tests de logique d'entreprise.

il ne semble pas logique de tester comment il est mis en œuvre. Il ne fait de sens de vérifier que lorsque le la notification devrait s'arrêter. Ainsi moins de code à couper, des tests plus rapides, du code plus propre.

3
répondu Vanuan 2017-11-27 12:14:32

la façon appropriée de faire ceci est d'utiliser Redux Thunk qui est un middleware populaire pour Redux, selon la documentation de Redux Thunk:

" Redux Thunk middleware vous permet d'écrire les créateurs d'action que retournez une fonction au lieu d'une action. Le thunk peut être utilisé pour retarder l'envoi d'une action, ou à l'envoi que si une certaine condition est rencontré. La fonction interne reçoit les méthodes de stockage expédition et getState en tant que paramètres".

donc en gros il renvoie une fonction, et vous pouvez retarder votre expédition ou la mettre dans un état de condition.

donc quelque chose comme ça va faire le travail pour vous:

import ReduxThunk from 'redux-thunk';

const INCREMENT_COUNTER = 'INCREMENT_COUNTER';

function increment() {
  return {
    type: INCREMENT_COUNTER
  };
}

function incrementAsync() {
  return dispatch => {
    setTimeout(() => {
      // Yay! Can invoke sync or async actions with `dispatch`
      dispatch(increment());
    }, 5000);
  };
}
3
répondu Alireza 2018-03-08 12:09:12

Redux lui-même est une jolie bibliothèque verbeuse, et pour ce genre de choses , vous auriez à utiliser quelque chose comme Redux-thunk , qui donnera une fonction dispatch , de sorte que vous serez en mesure d'envoyer la fermeture de la notification après plusieurs secondes.

j'ai créé une bibliothèque pour traiter des questions comme la verbosité et la composabilité, et votre exemple ressemblera à ce qui suit:

import { createTile, createSyncTile } from 'redux-tiles';
import { sleep } from 'delounce';

const notifications = createSyncTile({
  type: ['ui', 'notifications'],
  fn: ({ params }) => params.data,
  // to have only one tile for all notifications
  nesting: ({ type }) => [type],
});

const notificationsManager = createTile({
  type: ['ui', 'notificationManager'],
  fn: ({ params, dispatch, actions }) => {
    dispatch(actions.ui.notifications({ type: params.type, data: params.data }));
    await sleep(params.timeout || 5000);
    dispatch(actions.ui.notifications({ type: params.type, data: null }));
    return { closed: true };
  },
  nesting: ({ type }) => [type],
});

So nous composons des actions de synchronisation pour afficher les notifications à l'intérieur de l'action async, qui peuvent demander des informations sur l'arrière-plan, ou vérifier plus tard si la notification a été fermée manuellement.

1
répondu Bloomca 2017-06-04 15:16:26

c'est simple. utilisez le paquet trim-redux et écrivez comme ceci dans componentDidMout ou un autre endroit et tuez-le dans componentWillUnmount.

componentDidMount(){
   this.tm =  setTimeout(function(){ 
                      setStore({ age: 20 });
              }, 3000);
}

componentWillUnmount(){
   clearTimeout(this.tm);
}
1
répondu Mohmmad Ebrahimi Aval 2018-05-11 14:21:19

chaque fois que vous faites setTimeout s'il Vous Plaît assurez - vous que vous aussi effacer le temps d'arrêt en utilisant clearTimeout lorsque votre composant un mounts in componentWillUnMount méthode du cycle de vie

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

componentWillUnMount(){
   clearTimeout(this.timeout);
}
0
répondu Think-Twice 2018-08-27 04:06:35