1067 votes

Comment dispatcher une action Redux avec un délai d'attente ?

J'ai une action qui met à jour l'état de notification de mon application. Habituellement, cette notification sera une erreur ou une information quelconque. Je dois ensuite envoyer une autre action après 5 secondes qui ramènera l'état de notification à l'état initial, donc sans notification. La raison principale est de fournir une fonctionnalité où les notifications disparaissent automatiquement après 5 secondes.

Je n'ai pas eu de chance en utilisant setTimeout et de renvoyer une autre action, mais je n'arrive pas à trouver comment faire en ligne. Tout conseil est donc le bienvenu.

35 votes

N'oubliez pas de consulter mon redux-saga si vous voulez quelque chose de mieux que les thunks. Une réponse tardive qui vous oblige à faire défiler les pages longtemps avant de la voir apparaître :) ne signifie pas qu'elle ne vaut pas la peine d'être lue. Voici un raccourci : stackoverflow.com/a/38574266/82609

8 votes

Chaque fois que vous faites setTimeout, n'oubliez pas d'effacer le temporisateur en utilisant clearTimeout dans la méthode du cycle de vie componentWillUnMount.

3 votes

Redux-saga est cool mais ils ne semblent pas avoir de support pour les réponses typées des fonctions de générateur. Cela pourrait avoir de l'importance si vous utilisez typescript avec react.

2991voto

Dan Points 16670

Ne tombez pas dans le le piège de penser qu'une bibliothèque doit prescrire comment tout faire . Si vous voulez faire quelque chose avec un timeout en JavaScript, vous devez utiliser setTimeout . Il n'y a aucune raison pour que les actions Redux soient différentes.

Redux fait offrent d'autres moyens de traiter les éléments asynchrones, mais vous ne devriez les utiliser que lorsque vous vous rendez compte que vous répétez trop de code. À moins que vous ne soyez confronté à ce problème, utilisez ce que le langage propose et optez pour la solution la plus simple.

Écrire du code asynchrone en ligne

C'est de loin la méthode la 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, depuis 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 vous obtenez soit dispatch() ou des créateurs d'actions spécifiques injectés en tant que props. Cependant, cela ne fait aucune différence pour nous.

Si vous n'aimez pas faire des fautes de frappe en répartissant les mêmes actions à partir de différents composants, vous pouvez extraire les créateurs d'action au lieu de répartir les 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 préalablement liés avec connect() :

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

Jusqu'à présent, nous n'avons utilisé aucun intergiciel ou autre concept avancé.

Extraction du créateur d'actions asynchrones

L'approche ci-dessus fonctionne bien dans les cas simples, mais vous pouvez constater qu'elle présente quelques problèmes :

  • Cela vous oblige à dupliquer cette logique partout où vous voulez afficher une notification.
  • Les notifications n'ont pas d'ID, ce qui entraîne une situation de course si vous affichez deux notifications assez rapidement. Lorsque le premier délai d'attente se termine, il distribue HIDE_NOTIFICATION en cachant par erreur la deuxième notification avant la fin du délai.

Pour résoudre ces problèmes, il faudrait extraire une fonction qui centralise la logique du délai d'attente et dispatche ces deux actions. Cela 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 timeout ID and call
  // clearTimeout(), but we’d still want to do it in a single place.
  const id = nextNotificationId++
  dispatch(showNotification(id, text))

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

Les composants peuvent maintenant 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 est-ce que showNotificationWithTimeout() accepter dispatch comme premier argument ? Parce qu'il doit envoyer des actions au magasin. Normalement, un composant a accès à dispatch mais puisque nous voulons qu'une fonction externe prenne le contrôle de la répartition, nous devons lui donner le contrôle de la répartition.

Si vous aviez un magasin singleton exporté par un module, vous pourriez simplement 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 que cela oblige le magasin à être un singleton . Cela rend la mise en œuvre très difficile rendu du serveur . Sur le serveur, vous voudrez que chaque requête ait son propre magasin, de sorte que les différents utilisateurs obtiennent des données préchargées différentes.

Un magasin singleton rend également les tests plus difficiles. Vous ne pouvez plus simuler un magasin lorsque vous testez des créateurs d'action parce qu'ils font référence à un magasin réel spécifique exporté par un module spécifique. Vous ne pouvez même pas réinitialiser son état depuis l'extérieur.

Ainsi, bien que vous puissiez techniquement exporter un magasin singleton à partir d'un module, nous vous déconseillons de le faire. Ne le faites que si vous êtes sûr que votre application n'ajoutera jamais de rendu de serveur.

Revenir à 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.')    

Cela résout les problèmes de duplication de la logique et nous évite les conditions de course.

Logiciel intermédiaire Thunk

Pour les applications simples, cette approche devrait suffire. Ne vous préoccupez pas de l'intergiciel si vous en êtes satisfait.

Dans les applications plus importantes, cependant, vous pourriez trouver certains inconvénients.

Par exemple, il semble malheureux que nous devions passer dispatch autour. Il est donc plus difficile de séparer le conteneur et les composants de présentation parce que tout composant qui distribue les actions Redux de manière asynchrone de la manière ci-dessus doit accepter dispatch comme un accessoire pour qu'il puisse le passer plus loin. Vous ne pouvez pas simplement lier les créateurs d'action avec connect() plus parce que showNotificationWithTimeout() n'est pas vraiment un créateur d'actions. Il ne retourne pas une action Redux.

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

C'est ce qui a motivé trouver un moyen de "légitimer" ce mode de fourniture dispatch à une fonction d'aide, et aider Redux à "voir" de tels créateurs d'actions asynchrones comme un cas spécial de créateurs d'actions normaux. plutôt que des fonctions totalement différentes.

Si vous êtes toujours parmi nous et que vous reconnaissez également un problème dans votre application, vous pouvez utiliser le bouton Redux Thunk intergiciel.

En résumé, Redux Thunk apprend à Redux à reconnaître des 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 ce middleware est activé, si vous distribuez une fonction le middleware Redux Thunk lui donnera dispatch comme un argument. Il "avale" également de telles actions ; ne vous inquiétez donc pas de voir vos réducteurs recevoir 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, cela nous permet de déclarer showNotificationWithTimeout() comme un créateur d'action Redux ordinaire :

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

Notez que la fonction est presque identique à celle que nous avons écrite dans la section précédente. Cependant, elle n'accepte pas dispatch comme premier argument. Au lieu de cela, il renvoie à une fonction qui accepte dispatch comme premier argument.

Comment l'utiliserions-nous dans notre composante ? Sans aucun doute, nous pourrions écrire ceci :

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

Nous appelons le créateur d'action asynchrone pour obtenir la fonction interne qui veut juste dispatch et ensuite on passe dispatch .

Cependant, c'est encore plus gênant que la version originale ! Pourquoi avons-nous fait ça ?

A cause de ce que je vous ai dit avant. Si le middleware Redux Thunk est activé, chaque fois que vous essayez de distribuer une fonction au lieu d'un objet d'action, le middleware appellera cette fonction avec dispatch elle-même comme premier argument .

On peut donc 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) ne semble pas différent de l'envoi d'une action unique de manière synchrone au composant. C'est une bonne chose car les composants ne devraient pas se soucier de savoir si quelque chose se passe de manière synchrone ou asynchrone. Nous avons simplement fait abstraction de cela.

Remarquez que depuis que nous avons "appris" à Redux à reconnaître de tels créateurs d'actions "spéciales" (nous les appelons jeté ), nous pouvons maintenant les utiliser dans tous les endroits où nous utiliserions des créateurs d'action ordinaires. 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)

Lire l'État dans les Thunks

En général, vos reducers contiennent la logique métier permettant de déterminer l'état suivant. Cependant, les réducteurs n'interviennent qu'après la distribution des actions. Que faire si vous avez un effet secondaire (tel que l'appel d'une API) dans un créateur d'action thunk, et que vous voulez l'empêcher sous certaines conditions ?

Sans utiliser le middleware thunk, il suffit de faire cette vérification à l'intérieur du composant :

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

Cependant, l'intérêt d'extraire un créateur d'action était de centraliser cette logique répétitive sur de nombreux composants. Heureusement, Redux Thunk vous offre un moyen de lire l'état actuel du magasin Redux. En plus de dispatch il passe également getState comme deuxième argument de la fonction que vous retournez depuis 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 modèle. Il permet d'éviter les appels d'API lorsque des données sont disponibles en mémoire cache, mais il ne constitue pas une très bonne base pour construire votre logique métier. Si vous utilisez getState() seulement pour distribuer conditionnellement différentes actions, envisagez plutôt de placer la logique commerciale dans les réducteurs.

Les prochaines étapes

Maintenant que vous avez une intuition de base sur le fonctionnement des thunks, découvrez Redux exemple asynchrone qui les utilise.

Vous pouvez trouver de nombreux exemples dans lesquels les thunks renvoient des promesses. Ce n'est pas obligatoire mais cela peut être très pratique. Redux ne se soucie pas de ce que vous retournez d'un thunk, mais il vous donne sa valeur de retour à partir de dispatch() . C'est pourquoi vous pouvez renvoyer une Promise depuis un thunk et attendre qu'il se termine en appelant dispatch(someThunkReturningPromise()).then(...) .

Vous pouvez également diviser les créateurs d'actions thunk complexes en plusieurs créateurs d'actions thunk plus petits. Le site dispatch fournie par thunks peut accepter thunks lui-même, de sorte que vous pouvez appliquer le motif de manière récursive. Encore une fois, cela fonctionne mieux avec les Promesses car vous pouvez implémenter un flux de contrôle asynchrone par-dessus.

Pour certaines applications, vous pouvez vous trouver dans une situation où vos exigences en matière de flux de contrôle asynchrone sont trop complexes pour être exprimées par des thunks. Par exemple, la relance des requêtes qui ont échoué, le flux de réautorisation avec des jetons ou l'accueil étape par étape peuvent être trop verbeux et sujets à des erreurs lorsqu'ils sont écrits de cette façon. Dans ce cas, vous pouvez vous tourner vers des solutions de flux de contrôle asynchrones plus avancées, telles que les suivantes Saga Redux ou Boucle Redux . Évaluez-les, comparez les exemples correspondant à vos besoins et choisissez celui qui vous plaît le plus.

Enfin, n'utilisez rien (y compris les thunks) si vous n'en avez pas réellement besoin. N'oubliez pas que, selon les besoins, votre solution peut être aussi simple que

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

Ne vous en faites pas si vous ne savez pas pourquoi vous faites ça.

218voto

Sebastien Lorber Points 9682

Utilisation de Redux-saga

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

Cette réponse est un simple exemple, si vous voulez de meilleures explications sur les raisons pour lesquelles redux-saga peut être utile pour votre application, consultez le site suivant 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 asynchrone qui ressemble à du code synchrone (c'est pourquoi vous trouverez souvent des boucles while infinies dans Redux-saga). D'une certaine manière, Redux-saga construit son propre langage directement dans Javascript. Redux-saga peut sembler un peu difficile à apprendre au début, parce que vous devez avoir une compréhension de base des générateurs, mais aussi comprendre le langage offert par Redux-saga.

Je vais essayer de décrire ici le système de notification que j'ai construit au dessus de redux-saga. Cet exemple fonctionne actuellement en production.

Spécification du système de notification avancé

  • Vous pouvez demander l'affichage d'une notification
  • Vous pouvez demander à ce qu'une notification soit masquée
  • Une notification ne doit pas être affichée pendant 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, alors mettez-la en file d'attente/postponez-la.

Résultat

Capture d'écran de mon application de production Stample.co

toasts

Code

Ici, j'ai nommé la notification un toast mais c'est un détail de dénomination.

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 demandes, seules 3 notifications seront affichées, et la 4ème apparaîtra un peu plus tard, une fois que la 1ère notification aura disparu.

Notez que je ne recommande pas spécifiquement de dispatcher TOAST_DISPLAY_REQUESTED à partir de JSX. Il est préférable d'ajouter une autre saga qui écoute les événements de votre application existante, puis d'envoyer l'information à l'utilisateur. TOAST_DISPLAY_REQUESTED Le système de notification : votre composant qui déclenche la notification ne doit pas nécessairement être étroitement couplé au système de notification.

Conclusion

Mon code n'est pas parfait mais il fonctionne en production sans aucun bogue depuis des mois. Redux-saga et les générateurs sont un peu difficiles au début 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", il faut réduire le temps d'affichage de chaque notification pour que la taille de la file d'attente diminue plus rapidement.
  • détecter les changements de taille de la fenêtre et modifier le nombre maximal de notifications affichées en conséquence (par exemple, bureau=3, téléphone portrait = 2, téléphone paysage = 1)

Honnêtement, bonne chance pour implémenter ce genre de choses correctement avec les thunks.

Notez que vous pouvez faire exactement le même genre de chose avec redux-observable 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 RxJS.

31voto

Tyler Long Points 1216

Un référentiel avec des exemples de projets

Il y a actuellement quatre exemples de projets :

  1. Écrire du code asynchrone en ligne
  2. Extraction du créateur d'actions asynchrones
  3. Utiliser Redux Thunk
  4. Utiliser Redux Saga

La réponse acceptée est géniale.

Mais il y a quelque chose qui manque :

  1. Pas d'exemples de projets exécutables, juste quelques extraits de code.
  2. Pas d'échantillon de code pour d'autres alternatives, telles que :
    1. Saga Redux

J'ai donc créé le Bonjour Async référentiel pour ajouter les choses manquantes :

  1. Projets exécutables. Vous pouvez les télécharger et les exécuter sans modification.
  2. Fournir un exemple de code pour plus d'alternatives :

Saga Redux

La réponse acceptée fournit déjà des exemples d'extraits de code pour Async Code 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 n'est 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

Les sagas sont basées 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

Pour

  • Vous ne finirez pas dans l'enfer des rappels.
  • Vous pouvez tester facilement vos flux asynchrones.
  • Vos actions restent pures.

Cons

  • Il dépend des générateurs ES6, qui sont relativement nouveaux.

Veuillez vous référer à la projet exécutable si les extraits de code ci-dessus ne répondent pas à toutes vos questions.

25voto

Fatih Erikli Points 237

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

0 votes

Juste une petite question de suivi, quand on utilise un intergiciel applyMiddleware(ReduxPromise, thunk)(createStore) Est-ce que c'est comme ça que vous ajoutez plusieurs intergiciels (séparés par des virgules ?) car je n'arrive pas à faire fonctionner Thunk.

1 votes

@Ilja Cela devrait fonctionner : const store = createStore(reducer, applyMiddleware([ReduxPromise, thunk]));

24voto

Je vous recommande également de jeter un coup d'œil à la Modèle SAM .

Le modèle SAM préconise l'inclusion d'un "prédicat d'action suivante" où des actions (automatiques) telles que "les notifications disparaissent automatiquement après 5 secondes" sont déclenchées une fois que le modèle a été mis à jour (modèle SAM ~ état du réducteur + magasin).

Le patron préconise de séquencer les actions et les mutations du modèle une par une, car l'"état de contrôle" du modèle "contrôle" les actions qui sont activées et/ou exécutées automatiquement par le prédicat next-action. Vous ne pouvez tout simplement pas prédire (en général) quel sera l'état du système avant le traitement d'une action et donc si votre prochaine action attendue sera autorisée/possible.

Donc 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, car le fait qu'une action hideNotification puisse être envoyée dépend de l'acceptation par le modèle de la valeur "showNotication : true". Il pourrait y avoir d'autres parties du modèle qui l'empêchent de l'accepter et donc, il n'y aurait aucune raison de déclencher l'action hideNotification.

Je recommande vivement d'implémenter un prédicat next-action approprié après les mises à jour du magasin et la connaissance du nouvel état de contrôle du modèle. C'est le moyen le plus sûr d'implémenter le comportement que vous recherchez.

Vous pouvez nous rejoindre sur Gitter si vous le souhaitez. Il y a aussi un Guide de démarrage SAM disponible ici .

0 votes

Je n'ai fait qu'effleurer la surface jusqu'à présent, mais je suis déjà enthousiasmé par le motif SAM. V = S( vm( M.present( A(data) ) ), nap(M)) est tout simplement magnifique. Merci de partager vos pensées et votre expérience. Je vais creuser davantage.

0 votes

@ftor, merci ! quand je l'ai écrit la première fois, j'ai eu le même sentiment. J'utilise SAM en production depuis près d'un an maintenant, et je ne me souviens pas d'un moment où j'ai ressenti le besoin d'une bibliothèque pour implémenter SAM (même vdom, bien que je puisse voir quand elle pourrait être utilisée). Une seule ligne de code, c'est tout ! SAM produit un code isomorphe, il n'y a pas d'ambiguïté sur la façon de traiter les appels asynchrones... Je ne me souviens pas d'un moment où j'ai pensé : "Qu'est-ce que je fais ?

0 votes

SAM est un véritable modèle de génie logiciel (je viens de produire un SDK Alexa avec ce modèle). Il est basé sur TLA+ et tente d'apporter la puissance de ce travail incroyable à chaque développeur. SAM corrige trois approximations que (presque) tout le monde utilise depuis des décennies : - les actions peuvent manipuler l'état de l'application - les assignations sont équivalentes à la mutation - il n'y a pas de définition précise de ce qu'est une étape de programmation (par exemple, est-ce que a = b * c est une étape, est-ce que 1/ lire b,c 2/ calculer b*c, 3/ assigner a avec le résultat sont trois étapes différentes ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X