Selon les documents, "sans middleware, le magasin Redux ne prend en charge que le flux de données synchrone" . Je ne comprends pas pourquoi c'est le cas. Pourquoi le composant conteneur ne peut-il pas appeler l'API asynchrone, puisdispatch
les actions?
Par exemple, imaginez une interface utilisateur simple: un champ et un bouton. Lorsque l'utilisateur appuie sur le bouton, le champ est rempli de données provenant d'un serveur distant.
import * as React from 'react';
import * as Redux from 'redux';
import { Provider, connect } from 'react-redux';
const ActionTypes = {
STARTED_UPDATING: 'STARTED_UPDATING',
UPDATED: 'UPDATED'
};
class AsyncApi {
static getFieldValue() {
const promise = new Promise((resolve) => {
setTimeout(() => {
resolve(Math.floor(Math.random() * 100));
}, 1000);
});
return promise;
}
}
class App extends React.Component {
render() {
return (
<div>
<input value={this.props.field}/>
<button disabled={this.props.isWaiting} onClick={this.props.update}>Fetch</button>
{this.props.isWaiting && <div>Waiting...</div>}
</div>
);
}
}
App.propTypes = {
dispatch: React.PropTypes.func,
field: React.PropTypes.any,
isWaiting: React.PropTypes.bool
};
const reducer = (state = { field: 'No data', isWaiting: false }, action) => {
switch (action.type) {
case ActionTypes.STARTED_UPDATING:
return { ...state, isWaiting: true };
case ActionTypes.UPDATED:
return { ...state, isWaiting: false, field: action.payload };
default:
return state;
}
};
const store = Redux.createStore(reducer);
const ConnectedApp = connect(
(state) => {
return { ...state };
},
(dispatch) => {
return {
update: () => {
dispatch({
type: ActionTypes.STARTED_UPDATING
});
AsyncApi.getFieldValue()
.then(result => dispatch({
type: ActionTypes.UPDATED,
payload: result
}));
}
};
})(App);
export default class extends React.Component {
render() {
return <Provider store={store}><ConnectedApp/></Provider>;
}
}
Lorsque le composant exporté est rendu, je peux cliquer sur le bouton et l'entrée est correctement mise à jour.
Notez la update
fonction dans leconnect
appel. Il envoie une action qui indique à l'application qu'il est en train de mettre à jour, puis effectue un appel asynchrone. Une fois l'appel terminé, la valeur fournie est envoyée en tant que charge utile d'une autre action.
Quel est le problème avec cette approche? Pourquoi voudrais-je utiliser Redux Thunk ou Redux Promise, comme le suggère la documentation?
EDIT: J'ai cherché dans le dépôt Redux des indices et j'ai découvert que les créateurs d'actions devaient être de simples fonctions dans le passé. Par exemple, voici un utilisateur essayant de fournir une meilleure explication du flux de données asynchrone:
Le créateur d'action lui-même est toujours une fonction pure, mais la fonction thunk qu'il renvoie n'a pas besoin d'être, et il peut effectuer nos appels asynchrones
Les créateurs d'actions ne doivent plus être purs. Donc, le middleware thunk / promise était définitivement nécessaire dans le passé, mais il semble que ce ne soit plus le cas?
la source
Réponses:
Il n'y a rien de mal à cette approche. C'est juste gênant dans une grande application, car vous aurez différents composants effectuant les mêmes actions, vous voudrez peut-être rebondir certaines actions, ou garder un état local comme les ID d'incrémentation automatique à proximité des créateurs d'actions, etc. le point de vue de la maintenance pour extraire les créateurs d'actions dans des fonctions distinctes.
Vous pouvez lire ma réponse à «Comment envoyer une action Redux avec un délai d'expiration» pour une procédure plus détaillée.
Un middleware comme Redux Thunk ou Redux Promise ne vous donne que du «sucre de syntaxe» pour l'envoi de thunks ou de promesses, mais vous n'êtes pas obligé de l' utiliser.
Ainsi, sans middleware, votre créateur d'action pourrait ressembler à
Mais avec Thunk Middleware, vous pouvez l'écrire comme ceci:
Il n'y a donc pas de différence énorme. Une chose que j'aime dans cette dernière approche est que le composant ne se soucie pas que le créateur d'action soit asynchrone. Il appelle simplement
dispatch
normalement, il peut également utilisermapDispatchToProps
pour lier un tel créateur d'action avec une courte syntaxe, etc. Les composants ne savent pas comment les créateurs d'action sont implémentés, et vous pouvez basculer entre différentes approches asynchrones (Redux Thunk, Redux Promise, Redux Saga ) sans changer les composants. D'un autre côté, avec l'ancienne approche explicite, vos composants savent exactement qu'un appel spécifique est asynchrone et doitdispatch
être passé par une convention (par exemple, en tant que paramètre de synchronisation).Pensez également à la façon dont ce code va changer. Disons que nous voulons avoir une deuxième fonction de chargement des données et les combiner en un seul créateur d'actions.
Avec la première approche, nous devons être conscients du type de créateur d'action que nous appelons:
Avec Redux Thunk, les créateurs d'actions peuvent être
dispatch
le résultat d'autres créateurs d'actions et ne pas même se demander s'ils sont synchrones ou asynchrones:Avec cette approche, si vous souhaitez que vos créateurs d'actions examinent plus tard l'état actuel de Redux, vous pouvez simplement utiliser le deuxième
getState
argument passé aux thunks sans modifier le code appelant:Si vous devez le modifier pour qu'il soit synchrone, vous pouvez également le faire sans modifier aucun code d'appel:
Ainsi, l'avantage d'utiliser un middleware comme Redux Thunk ou Redux Promise est que les composants ne savent pas comment les créateurs d'actions sont implémentés, s'ils se soucient de l'état de Redux, s'ils sont synchrones ou asynchrones, et s'ils appellent ou non d'autres créateurs d'actions. . L'inconvénient est un peu d'indirection, mais nous pensons que cela en vaut la peine dans les applications réelles.
Enfin, Redux Thunk and friends n'est qu'une approche possible des demandes asynchrones dans les applications Redux. Une autre approche intéressante est Redux Saga qui vous permet de définir des démons de longue durée («sagas») qui prennent des mesures au fur et à mesure et transforment ou exécutent des demandes avant de générer des actions. Cela déplace la logique des créateurs d'action vers les sagas. Vous voudrez peut-être le vérifier et choisir plus tard ce qui vous convient le mieux.
Ceci est une erreur. Les docs l'ont dit, mais les docs étaient faux.
Les créateurs d'actions n'ont jamais été tenus d'être de simples fonctions.
Nous avons corrigé les documents pour refléter cela.
la source
alert
aprèsdispatch()
ing l'action.loadData(this.props.dispatch, this.props.userId); // don't forget to pass dispatch
. Pourquoi dois-je passer en expédition? Si, par convention, il n'y a jamais qu'un seul magasin global, pourquoi ne pas simplement le référencer directement et le fairestore.dispatch
quand j'en ai besoin, par exemple, dansloadData
?store
instance différente pour chaque requête afin que vous ne puissiez pas la définir à l'avance.Non.
Mais ... vous devriez utiliser redux-saga :)
La réponse de Dan Abramov est juste,
redux-thunk
mais je parlerai un peu plus de la redux-saga qui est assez similaire mais plus puissante.Impératif VS déclaratif
redux-thunk
est impératif /redux-saga
est déclaratifLorsque vous avez un thunk entre vos mains, comme une monade d'E / S ou une promesse, vous ne pouvez pas facilement savoir ce qu'il fera une fois que vous aurez exécuté. La seule façon de tester un thunk est de l'exécuter et de se moquer du répartiteur (ou de tout le monde extérieur s'il interagit avec plus de choses ...).
Si vous utilisez des simulateurs, vous ne faites pas de programmation fonctionnelle.
La source
Les sagas (telles qu'elles ont été implémentées dans
redux-saga
) sont déclaratives et, comme les composants Free monad ou React, elles sont beaucoup plus faciles à tester sans aucune simulation.Voir aussi cet article :
(En fait, Redux-saga est comme un hybride: le flux est impératif mais les effets sont déclaratifs)
Confusion: actions / événements / commandes ...
Il y a beaucoup de confusion dans le monde du frontend sur la façon dont certains concepts de backend comme CQRS / EventSourcing et Flux / Redux peuvent être liés, principalement parce que dans Flux nous utilisons le terme "action" qui peut parfois représenter à la fois du code impératif (
LOAD_USER
) et des événements (USER_LOADED
). Je crois que, comme pour la recherche d'événements, vous ne devez envoyer que des événements.Utiliser les sagas dans la pratique
Imaginez une application avec un lien vers un profil utilisateur. La façon idiomatique de gérer cela avec chaque middleware serait:
redux-thunk
redux-saga
Cette saga se traduit par:
Comme vous pouvez le voir, il y a certains avantages à
redux-saga
.L'utilisation de
takeLatest
permis pour exprimer que vous êtes uniquement intéressé à obtenir les données du dernier nom d'utilisateur cliqué (gérer les problèmes de concurrence dans le cas où l'utilisateur clique très rapidement sur un grand nombre de noms d'utilisateur). Ce genre de choses est difficile avec les thunks. Vous auriez pu utilisertakeEvery
si vous ne vouliez pas ce comportement.Vous gardez les créateurs d'action purs. Notez qu'il est toujours utile de conserver actionCreators (dans les sagas
put
et les composantsdispatch
), car cela pourrait vous aider à ajouter la validation d'action (assertions / flux / typescript) à l'avenir.Votre code devient beaucoup plus testable car les effets sont déclaratifs
Vous n'avez plus besoin de déclencher des appels de type rpc comme
actions.loadUser()
. Votre interface utilisateur a juste besoin d'envoyer ce qui s'est passé. Nous ne tirons que des événements (toujours au passé!) Et non plus des actions. Cela signifie que vous pouvez créer des «canards» découplés ou des contextes délimités et que la saga peut servir de point de couplage entre ces composants modulaires.Cela signifie que vos vues sont plus faciles à gérer car elles n'ont plus besoin de contenir cette couche de traduction entre ce qui s'est passé et ce qui devrait se produire en tant qu'effet.
Par exemple, imaginez une vue de défilement infinie.
CONTAINER_SCROLLED
peut conduire àNEXT_PAGE_LOADED
, mais est-ce vraiment la responsabilité du conteneur déroulant de décider si nous devons ou non charger une autre page? Ensuite, il doit être conscient de choses plus compliquées comme si la dernière page a été chargée avec succès ou s'il y a déjà une page qui tente de se charger, ou s'il n'y a plus d'éléments à charger? Je ne pense pas: pour une réutilisation maximale, le conteneur déroulant devrait simplement décrire qu'il a été fait défiler. Le chargement d'une page est un "effet métier" de ce parcheminCertains pourraient faire valoir que les générateurs peuvent intrinsèquement masquer l'état en dehors du magasin redux avec des variables locales, mais si vous commencez à orchestrer des choses complexes à l'intérieur des thunks en démarrant des minuteurs, etc., vous auriez de toute façon le même problème. Et il y a un
select
effet qui permet désormais d'obtenir un état de votre boutique Redux.Les sagas peuvent être parcourues dans le temps et permettent également la journalisation des flux complexes et les outils de développement qui sont actuellement en cours d'élaboration. Voici une journalisation de flux asynchrone simple qui est déjà implémentée:
Découplage
Les sagas ne remplacent pas seulement les thunks redux. Ils proviennent du backend / des systèmes distribués / du sourcing d'événements.
C'est une idée fausse très répandue que les sagas sont juste là pour remplacer vos thunks redux avec une meilleure testabilité. En fait, ce n'est qu'un détail de mise en œuvre de redux-saga. L'utilisation des effets déclaratifs est meilleure que les thunks pour la testabilité, mais le modèle de saga peut être implémenté au-dessus du code impératif ou déclaratif.
En premier lieu, la saga est un logiciel qui permet de coordonner les transactions de longue durée (cohérence éventuelle) et les transactions dans différents contextes délimités (jargon de conception piloté par domaine).
Pour simplifier cela pour le monde frontal, imaginez qu'il existe widget1 et widget2. Lorsqu'un bouton sur widget1 est cliqué, cela devrait avoir un effet sur widget2. Au lieu de coupler les 2 widgets (c'est-à-dire widget1 envoyer une action qui cible widget2), widget1 envoie seulement que son bouton a été cliqué. Ensuite, la saga écoute ce bouton, puis met à jour widget2 en dissociant un nouvel événement dont widget2 a connaissance.
Cela ajoute un niveau d'indirection inutile pour les applications simples, mais facilite la mise à l'échelle d'applications complexes. Vous pouvez désormais publier widget1 et widget2 dans différents référentiels npm afin qu'ils n'aient jamais à se connaître, sans qu'ils aient à partager un registre global d'actions. Les 2 widgets sont maintenant des contextes bornés qui peuvent vivre séparément. Ils n'ont pas besoin les uns des autres pour être cohérents et peuvent également être réutilisés dans d'autres applications. La saga est le point de couplage entre les deux widgets qui les coordonnent de manière significative pour votre entreprise.
Quelques bons articles sur la façon de structurer votre application Redux, sur lesquels vous pouvez utiliser Redux-saga pour des raisons de découplage:
Un cas d'utilisation concret: le système de notification
Je veux que mes composants puissent déclencher l'affichage des notifications dans l'application. Mais je ne veux pas que mes composants soient fortement couplés au système de notification qui a ses propres règles métier (max 3 notifications affichées en même temps, file d'attente de notification, 4 secondes d'affichage etc ...).
Je ne veux pas que mes composants JSX décident quand une notification s'affichera / se cachera. Je lui donne juste la possibilité de demander une notification et de laisser les règles complexes à l'intérieur de la saga. Ce genre de choses est assez difficile à mettre en œuvre avec des thunks ou des promesses.
J'ai décrit ici comment cela peut être fait avec la saga
Pourquoi est-il appelé une saga?
Le terme saga vient du monde du backend. J'ai d'abord présenté Yassine (l'auteur de Redux-saga) à ce terme dans une longue discussion .
Initialement, ce terme a été introduit avec un document , le modèle de saga était censé être utilisé pour gérer la cohérence éventuelle des transactions distribuées, mais son utilisation a été étendue à une définition plus large par les développeurs backend afin qu'il couvre désormais également le "gestionnaire de processus" modèle (en quelque sorte le modèle de saga original est une forme spécialisée de gestionnaire de processus).
Aujourd'hui, le terme "saga" prête à confusion car il peut décrire 2 choses différentes. Comme il est utilisé dans redux-saga, il ne décrit pas un moyen de gérer les transactions distribuées mais plutôt un moyen de coordonner les actions dans votre application.
redux-saga
aurait également pu être appeléredux-process-manager
.Voir également:
Alternatives
Si vous n'aimez pas l'idée d'utiliser des générateurs mais que vous êtes intéressé par le modèle de saga et ses propriétés de découplage, vous pouvez également obtenir la même chose avec redux-observable qui utilise le nom
epic
pour décrire exactement le même modèle, mais avec RxJS. Si vous connaissez déjà Rx, vous vous sentirez comme chez vous.Quelques ressources utiles de redux-saga
Conseils 2017
yield put(someActionThunk)
si cela a du sens.Si vous avez peur d'utiliser Redux-saga (ou Redux-observable) mais que vous avez juste besoin du schéma de découplage, vérifiez redux-dispatch-subscribe : il permet d'écouter les dépêches et de déclencher de nouvelles dépêches dans l'écouteur.
la source
ADD_ITEM
, c'est impératif car vous envoyez une action qui vise à avoir un effet sur votre boutique: vous attendez de l'action qu'elle fasse quelque chose. Être déclaratif embrasse la philosophie de la recherche d'événements: vous ne répartissez pas les actions pour déclencher des changements sur vos applications, mais vous répartissez les événements passés pour décrire ce qui s'est passé dans votre application. L'envoi d'un événement devrait être suffisant pour considérer que l'état de la demande a changé. Le fait qu'il existe un magasin Redux qui réagit à l'événement est un détail d'implémentation facultatifLa réponse courte : me semble être une approche totalement raisonnable du problème d'asynchronie. Avec quelques mises en garde.
J'avais une pensée très similaire en travaillant sur un nouveau projet que nous venons de commencer à mon travail. J'étais un grand fan de l'élégant système de vanilla Redux pour mettre à jour le magasin et rendre les composants d'une manière qui reste en dehors des tripes d'une arborescence de composants React. Il me semblait étrange de me connecter à cet élégant
dispatch
mécanisme pour gérer l'asynchronie.J'ai fini par adopter une approche vraiment similaire à ce que vous avez dans une bibliothèque que j'ai exclue de notre projet, que nous avons appelé react-redux-controller .
J'ai fini par ne pas suivre l'approche exacte que vous avez ci-dessus pour quelques raisons:
dispatch
mêmes via une portée lexicale. Cela limite les options de refactorisation une fois que cetteconnect
instruction est incontrôlable - et cela semble assez difficile à gérer avec cette seuleupdate
méthode. Vous avez donc besoin d'un système pour vous permettre de composer ces fonctions de répartiteur si vous les divisez en modules séparés.Ensemble, vous devez configurer un système pour permettre
dispatch
et injecter le magasin dans vos fonctions de répartition, ainsi que les paramètres de l'événement. Je connais trois approches raisonnables à cette injection de dépendance:dispatch
approches middleware, mais je suppose qu'elles sont fondamentalement les mêmes.connect
, plutôt que d'avoir à travailler directement avec le magasin brut normalisé.this
contexte, à travers une variété de mécanismes possibles.Mise à jour
Il me vient à l'esprit qu'une partie de cette énigme est une limitation de react-redux . Le premier argument pour
connect
obtenir un instantané d'état, mais pas de répartition. Le deuxième argument obtient l'expédition mais pas l'État. Aucun des deux arguments n'obtient un thunk qui se ferme sur l'état actuel, pour pouvoir voir l'état mis à jour au moment d'une continuation / rappel.la source
Le but d'Abramov - et tout le monde est idéalement - est simplement d' encapsuler la complexité (et les appels asynchrones) à l'endroit où cela est le plus approprié .
Quel est le meilleur endroit pour le faire dans le flux de données Redux standard? Que diriez-vous:
la source
Pour répondre à la question posée au début:
Gardez à l'esprit que ces documents sont pour Redux, pas Redux plus React. Les magasins Redux connectés aux composants React peuvent faire exactement ce que vous dites, mais un magasin Plain Jane Redux sans middleware n'accepte pas d'arguments pour
dispatch
exclure les objets simples.Sans middleware, vous pourriez bien sûr toujours faire
Mais c'est un cas similaire où l'asynchronie est enroulée autour de Redux plutôt que gérée par Redux. Ainsi, le middleware permet l'asynchronie en modifiant ce qui peut être transmis directement à
dispatch
.Cela dit, l'esprit de votre suggestion est, je pense, valide. Il existe certainement d'autres façons de gérer l'asynchronie dans une application Redux + React.
L'un des avantages de l'utilisation du middleware est que vous pouvez continuer à utiliser les créateurs d'actions comme d'habitude sans vous soucier de la façon exacte dont ils sont connectés. Par exemple, en utilisant
redux-thunk
, le code que vous avez écrit ressemblerait beaucoup àqui ne semble pas si différent de l'original - c'est juste un peu mélangé - et
connect
ne sait pas queupdateThing
c'est (ou doit être) asynchrone.Si vous vouliez également soutenir des promesses , des observables , des sagas ou des créateurs d'action personnalisés et hautement déclaratifs , Redux peut le faire simplement en changeant ce que vous passez
dispatch
(aka, ce que vous retournez des créateurs d'action). Aucun nettoyage avec les composants React (ouconnect
appels) n'est nécessaire.la source
OK, commençons par voir comment le middleware fonctionne en premier, qui répond tout à fait à la question, voici le code source d'une fonction pplyMiddleWare dans Redux:
Regardez cette partie, voyez comment notre envoi devient une fonction .
OK, voici comment Redux-thunk , l'un des middlewares les plus utilisés pour Redux, se présente:
Donc, comme vous le voyez, il retournera une fonction à la place d'une action, ce qui signifie que vous pouvez attendre et l'appeler à tout moment car c'est une fonction ...
Alors qu'est-ce que c'est que le thunk? Voilà comment il est introduit dans Wikipedia:
Alors voyez à quel point le concept est simple et comment il peut vous aider à gérer vos actions asynchrones ...
C'est quelque chose que vous pouvez vivre sans, mais rappelez-vous que dans la programmation, il y a toujours de meilleures façons de faire les choses ...
la source
Utiliser Redux-saga est le meilleur middleware dans l'implémentation de React-redux.
Ex: store.js
Et puis saga.js
Et puis action.js
Et puis reducer.js
Et puis main.js
essayez ceci .. fonctionne
la source
Il existe des créateurs d'actions synchrones, puis des créateurs d'actions asynchrones.
Un créateur d'action synchrone est celui qui, lorsque nous l'appelons, renvoie immédiatement un objet Action avec toutes les données pertinentes attachées à cet objet et prêt à être traité par nos réducteurs.
Les créateurs d'actions asynchrones en sont un dans lequel il faudra un peu de temps avant d'être prêt à envoyer éventuellement une action.
Par définition, chaque fois que vous avez un créateur d'action qui fait une demande réseau, il va toujours être qualifié de créateur d'action asynchrone.
Si vous souhaitez avoir des créateurs d'actions asynchrones dans une application Redux, vous devez installer quelque chose appelé middleware qui vous permettra de traiter avec ces créateurs d'actions asynchrones.
Vous pouvez le vérifier dans le message d'erreur qui nous indique d'utiliser un middleware personnalisé pour les actions asynchrones.
Qu'est-ce qu'un middleware et pourquoi en avons-nous besoin pour le flux asynchrone dans Redux?
Dans le contexte d'un middleware redux tel que redux-thunk, un middleware nous aide à traiter avec les créateurs d'actions asynchrones car c'est quelque chose que Redux ne peut pas gérer hors de la boîte.
Avec un middleware intégré dans le cycle Redux, nous appelons toujours des créateurs d'actions, qui vont retourner une action qui sera envoyée mais maintenant quand nous expédions une action, plutôt que de l'envoyer directement à tous nos réducteurs, nous allons pour dire qu'une action sera envoyée à travers les différents middlewares de l'application.
À l'intérieur d'une seule application Redux, nous pouvons avoir autant ou aussi peu de middleware que nous voulons. Pour la plupart, dans les projets sur lesquels nous travaillons, nous aurons un ou deux middleware connectés à notre magasin Redux.
Un middleware est une simple fonction JavaScript qui sera appelée à chaque action que nous distribuons. À l'intérieur de cette fonction, un middleware a la possibilité d'empêcher une action d'être envoyée à l'un des réducteurs, il peut modifier une action ou simplement jouer avec une action de n'importe quelle manière, par exemple, nous pourrions créer un middleware qui consignera les journaux chaque action que vous envoyez juste pour votre plaisir visuel.
Il existe un grand nombre de middleware open source que vous pouvez installer en tant que dépendances dans votre projet.
Vous n'êtes pas limité à utiliser uniquement des middleware open source ou à les installer en tant que dépendances. Vous pouvez écrire votre propre middleware personnalisé et l'utiliser dans votre boutique Redux.
L'une des utilisations les plus populaires du middleware (et obtenir votre réponse) est de traiter avec les créateurs d'actions asynchrones, probablement le middleware le plus populaire est redux-thunk et il s'agit de vous aider à traiter avec les créateurs d'actions asynchrones.
Il existe de nombreux autres types de middleware qui vous aident également à gérer les créateurs d'actions asynchrones.
la source
Pour répondre à la question:
Je dirais pour au moins deux raisons:
La première raison est la séparation des préoccupations, ce n'est pas le travail de l'
action creator
appelerapi
et de récupérer les données, vous devez passer deux arguments à votreaction creator function
, leaction type
et unpayload
.La deuxième raison est que le
redux store
est en attente d'un objet simple avec le type d'action obligatoire et éventuellement unpayload
(mais ici, vous devez également transmettre la charge utile).Le créateur d'action doit être un simple objet comme ci-dessous:
Et le travail de
Redux-Thunk midleware
àdispache
la suite de votreapi call
au appropriéaction
.la source
Lorsque vous travaillez dans un projet d'entreprise, de nombreuses exigences sont disponibles dans le middleware, telles que (saga) non disponibles dans un flux asynchrone simple, en voici quelques-unes:
La liste est longue il suffit de revoir la section avancée de la documentation de la saga
la source