Je suis nouveau sur React/Redux. J'utilise un middleware fetch api dans l'application Redux pour traiter les API. C'est ( redux-api-middleware ). Je pense que c'est le bon moyen de traiter les actions d'API asynchrones. Mais je trouve des cas qui ne peuvent pas être résolus par moi-même.
Comme l'indique la page d'accueil ( Lifecycle ), le cycle de vie d'une API d'extraction commence par l'envoi d'une action CALL_API et se termine par l'envoi d'une action FSA.
Donc, mon premier cas montre/cache un préchargeur lors de la récupération des API. Le middleware enverra une action de la FSA au début et une action de la FSA à la fin. Les deux actions sont reçues par les réducteurs qui ne devraient effectuer que certains traitements de données normaux. Pas d'opérations d'interface utilisateur, plus d'opérations. Peut-être devrais-je sauvegarder l'état de traitement en état puis les rendre lors de la mise à jour du magasin.
Mais comment faire ça? Un flux de composant réactif sur toute la page? que se passe-t-il avec la mise à jour du magasin à partir d'autres actions? Je veux dire qu'ils sont plus comme des événements que des états!
Pire encore, que dois-je faire si je dois utiliser le dialogue de confirmation natif ou le dialogue d'alerte dans les applications redux/react? Où devraient-ils être placés, actions ou réducteurs?
Meilleurs vœux! Souhait de répondre.
Je veux dire qu'ils sont plus comme des événements que des états!
Je ne le dirais pas. Je pense que les indicateurs de charge sont un excellent cas d’UI qui est facilement décrit en fonction de l’état: dans ce cas, d’une variable booléenne. Bien que cette réponse soit correcte, je voudrais fournir un code pour l’accompagner.
Dans le async
exemple dans le référentiel Redux , réducteur met à jour un champ appelé isFetching
:
case REQUEST_POSTS:
return Object.assign({}, state, {
isFetching: true,
didInvalidate: false
})
case RECEIVE_POSTS:
return Object.assign({}, state, {
isFetching: false,
didInvalidate: false,
items: action.posts,
lastUpdated: action.receivedAt
Le composant utilise connect()
de React Redux pour s'abonner à l'état du magasin et renvoie isFetching
dans le cadre de la valeur mapStateToProps()
de retour est disponible dans les accessoires du composant connecté:
function mapStateToProps(state) {
const { selectedReddit, postsByReddit } = state
const {
isFetching,
lastUpdated,
items: posts
} = postsByReddit[selectedReddit] || {
isFetching: true,
items: []
}
return {
selectedReddit,
posts,
isFetching,
lastUpdated
}
}
Enfin, le composant tilise isFetching
prop dans la fonction render()
pour générer un libellé "Loading ..." (qui pourrait éventuellement être une visière):
{isEmpty
? (isFetching ? <h2>Loading...</h2> : <h2>Empty.</h2>)
: <div style={{ opacity: isFetching ? 0.5 : 1 }}>
<Posts posts={posts} />
</div>
}
Pire encore, que dois-je faire si je dois utiliser le dialogue de confirmation natif ou le dialogue d'alerte dans les applications redux/react? Où devraient-ils être placés, actions ou réducteurs?
Tous les effets secondaires (et afficher un dialogue est très certainement un effet secondaire) n'appartiennent pas aux réducteurs. Considérez les réducteurs comme des "bâtisseurs d’État" passifs. Ils ne font pas vraiment des choses.
Si vous souhaitez afficher une alerte, faites-le depuis un composant avant d'envoyer une action ou faites-le depuis un créateur d'action. Au moment où une action est envoyée, il est trop tard pour y appliquer des effets secondaires.
Pour chaque règle, il y a une exception. Parfois, la logique de vos effets secondaires est tellement compliquée que vous souhaitez les associer soit à des types d’action spécifiques, soit à des réducteurs spécifiques. Dans ce cas, consultez des projets avancés tels que Redux Saga et Redux Loop . Ne le faites que lorsque vous êtes à l’aise avec Vanilla Redux et que vous avez un réel problème d’effets secondaires épars que vous aimeriez rendre plus gérable.
Excellente réponse Dan Abramov! Je veux juste ajouter que je faisais plus ou moins exactement cela dans une de mes applications (conserver isFetching en tant que booléen) et que je devais en faire un entier (qui finit par lire comme le nombre de demandes en attente) pour prendre en charge plusieurs messages simultanés. demandes.
avec booléen:
demande 1 commence -> spinner sur -> demande 2 commence -> demande 1 se termine -> spinner off -> demande 2 se termine
avec entier:
demande 1 commence -> spinner on -> demande 2 commence -> demande 1 se termine -> demande 2 se termine -> spinner off
case REQUEST_POSTS:
return Object.assign({}, state, {
isFetching: state.isFetching + 1,
didInvalidate: false
})
case RECEIVE_POSTS:
return Object.assign({}, state, {
isFetching: state.isFetching - 1,
didInvalidate: false,
items: action.posts,
lastUpdated: action.receivedAt
J'aimerais ajouter quelque chose. L'exemple du monde réel utilise un champ isFetching
dans le magasin pour représenter le moment où une collection éléments est extraite. Toute collection est généralisée à un réducteur pagination
qui peut être connecté à vos composants pour suivre l’état et indiquer si une collection est en cours de chargement.
Il m'est arrivé de chercher des détails pour une entité spécifique qui ne correspond pas au modèle de pagination. Je voulais avoir un état représentant si les détails sont extraits du serveur, mais je ne voulais pas non plus avoir un réducteur juste pour cela.
Pour résoudre ce problème, j'ai ajouté un autre réducteur générique appelé fetching
. Cela fonctionne de la même manière que le réducteur de pagination et sa responsabilité consiste simplement à regarder un ensemble d’actions et à générer un nouvel état avec des paires [entity, isFetching]
. Cela permet de connect
le réducteur à n’importe quel composant et de savoir si l’application récupère actuellement des données non seulement pour une collection, mais également pour une entité spécifique.
Je n’étais pas tombé sur cette question jusqu’à présent, mais comme aucune réponse n’est acceptée, je jetterai mon chapeau. J'ai écrit un outil pour ce travail même: react-loader-factory . C'est un peu plus complexe que la solution d'Abramov, mais il est plus modulaire et plus pratique, car je ne voulais pas avoir à réfléchir après l'avoir écrit.
Il y a quatre grandes pièces:
const loaderWrapper = loaderFactory(actionsList, monitoredStates);
connect()
renvoie dans Redux), afin que vous puissiez simplement le boulonner sur votre matériel existant. const LoadingChild = loaderWrapper(ChildComponent);
ACTION_SUCCESS
et ACTION_REQUEST
, par exemple). (Vous pouvez bien sûr envoyer des actions ailleurs et simplement surveiller à partir du wrapper.)Le module lui-même est indépendant du middleware redux-api, mais c'est avec cela que je l'utilise, voici donc un exemple de code tiré du fichier README:
Un composant avec un chargeur l'enveloppant:
import React from 'react';
import { myAsyncAction } from '../actions';
import loaderFactory from 'react-loader-factory';
import ChildComponent from './ChildComponent';
const actionsList = [myAsyncAction()];
const monitoredStates = ['ASYNC_REQUEST'];
const loaderWrapper = loaderFactory(actionsList, monitoredStates);
const LoadingChild = loaderWrapper(ChildComponent);
const containingComponent = props => {
// Do whatever you need to do with your usual containing component
const childProps = { someProps: 'props' };
return <LoadingChild { ...childProps } />;
}
Un réducteur à surveiller par le chargeur (bien que vous puissiez le câbler différemment si vous le souhaitez):
export function activeRequests(state = [], action) {
const newState = state.slice();
// regex that tests for an API action string ending with _REQUEST
const reqReg = new RegExp(/^[A-Z]+\_REQUEST$/g);
// regex that tests for a API action string ending with _SUCCESS
const sucReg = new RegExp(/^[A-Z]+\_SUCCESS$/g);
// if a _REQUEST comes in, add it to the activeRequests list
if (reqReg.test(action.type)) {
newState.Push(action.type);
}
// if a _SUCCESS comes in, delete its corresponding _REQUEST
if (sucReg.test(action.type)) {
const reqType = action.type.split('_')[0].concat('_REQUEST');
const deleteInd = state.indexOf(reqType);
if (deleteInd !== -1) {
newState.splice(deleteInd, 1);
}
}
return newState;
}
Je pense que dans un avenir proche, j'ajouterai des éléments tels que timeout et error au module, mais le modèle ne sera pas très différent.
La réponse courte à votre question est la suivante:
Suis-je le seul à penser que les indicateurs de charge n'appartiennent pas à un magasin Redux? Je veux dire, je ne pense pas que cela fait partie de l'état d'une application en soi ..
Maintenant, je travaille avec Angular2, et ce que je fais, c’est que j’ai un service "Loading" qui expose différents indicateurs de chargement via RxJS BehaviourSubjects .. Je suppose que le mécanisme est le même, je ne stocke pas les informations dans Redux.
Les utilisateurs de LoadingService s’abonnent simplement aux événements qu’ils souhaitent écouter.
Les créateurs de mes actions Redux appellent le LoadingService chaque fois que des modifications sont nécessaires. Les composants UX souscrivent aux observables exposés ...
Nous avons trois types de notifications dans notre application, qui sont tous conçus en tant qu'aspects:
Ces trois éléments sont au niveau supérieur de notre application (principal) et câblés via Redux, comme indiqué dans l'extrait de code ci-dessous. Ces accessoires contrôlent l'affichage de leurs aspects correspondants.
J'ai conçu un proxy qui gère tous nos appels d'API. Ainsi, toutes les erreurs isFetching et (api) sont transmises à actionCreators que j'importe dans le proxy. (En passant, j'utilise également webpack pour injecter une maquette du service de support pour dev afin que nous puissions travailler sans dépendances du serveur.)
Tout autre endroit de l'application qui doit fournir n'importe quel type de notification importe simplement l'action appropriée. Snackbar & Error ont des paramètres pour les messages à afficher.
@connect(
// map state to props
state => ({
isFetching :state.main.get('isFetching'), // ProgressIndicator
notification :state.main.get('notification'), // Snackbar
error :state.main.get('error') // ErrorPopup
}),
// mapDispatchToProps
(dispatch) => { return {
actions: bindActionCreators(actionCreators, dispatch)
}}
) la classe d'exportation par défaut principale s'étend React.Component {
Vous pouvez ajouter des écouteurs de modification à vos magasins en utilisant la méthode connect()
de React Redux ou la méthode de bas niveau store.subscribe()
. Vous devez avoir l'indicateur de chargement dans votre magasin, que le gestionnaire de changement de magasin peut ensuite vérifier et mettre à jour l'état du composant. Le composant restitue ensuite le préchargeur si nécessaire, en fonction de l'état.
alert
et confirm
ne devrait pas être un problème. Ils bloquent et alerte ne prend même aucune entrée de l'utilisateur. Avec confirm
, vous pouvez définir l'état en fonction des éléments sur lesquels l'utilisateur a cliqué si le choix de l'utilisateur affecte le rendu du composant. Sinon, vous pouvez stocker le choix en tant que variable de membre du composant pour une utilisation ultérieure.
Je sauve les URL tels que:
isFetching: {
/api/posts/1: true,
api/posts/3: false,
api/search?q=322: true,
}
Et puis j'ai un sélecteur mémorisé (via resélectionner).
const getIsFetching = createSelector(
state => state.isFetching,
items => items => Object.keys(items).filter(item => items[item] === true).length > 0 ? true : false
);
Pour rendre l'URL unique en cas de POST, je passe une variable en tant que requête.
Et là où je veux montrer un indicateur, j'utilise simplement la variable getFetchCount