Il y a beaucoup de discussions sur le dernier enfant de la ville de redux en ce moment, redux-saga / redux-saga . Il utilise des fonctions de générateur pour écouter / répartir les actions.
Avant d'envelopper ma tête, j'aimerais connaître les avantages / inconvénients de l'utilisation redux-saga
au lieu de l'approche ci-dessous où j'utilise redux-thunk
avec async / wait.
Un composant peut ressembler à ceci, répartir les actions comme d'habitude.
import { login } from 'redux/auth';
class LoginForm extends Component {
onClick(e) {
e.preventDefault();
const { user, pass } = this.refs;
this.props.dispatch(login(user.value, pass.value));
}
render() {
return (<div>
<input type="text" ref="user" />
<input type="password" ref="pass" />
<button onClick={::this.onClick}>Sign In</button>
</div>);
}
}
export default connect((state) => ({}))(LoginForm);
Ensuite, mes actions ressemblent à ceci:
// auth.js
import request from 'axios';
import { loadUserData } from './user';
// define constants
// define initial state
// export default reducer
export const login = (user, pass) => async (dispatch) => {
try {
dispatch({ type: LOGIN_REQUEST });
let { data } = await request.post('/login', { user, pass });
await dispatch(loadUserData(data.uid));
dispatch({ type: LOGIN_SUCCESS, data });
} catch(error) {
dispatch({ type: LOGIN_ERROR, error });
}
}
// more actions...
// user.js
import request from 'axios';
// define constants
// define initial state
// export default reducer
export const loadUserData = (uid) => async (dispatch) => {
try {
dispatch({ type: USERDATA_REQUEST });
let { data } = await request.get(`/users/${uid}`);
dispatch({ type: USERDATA_SUCCESS, data });
} catch(error) {
dispatch({ type: USERDATA_ERROR, error });
}
}
// more actions...
javascript
reactjs
redux
redux-thunk
redux-saga
hampusohlsson
la source
la source
::
avant dethis.onClick
faire?this
), aliasthis.onClick = this.onClick.bind(this)
. La forme plus longue est généralement recommandée dans le constructeur, car le raccourci se lie à nouveau à chaque rendu.bind()
beaucoup pour passerthis
à la fonction, mais j'ai commencé à utiliser() => method()
maintenant.Réponses:
Dans redux-saga, l'équivalent de l'exemple ci-dessus serait
La première chose à noter est que nous appelons les fonctions api en utilisant le formulaire
yield call(func, ...args)
.call
n'exécute pas l'effet, il crée simplement un objet simple comme{type: 'CALL', func, args}
. L'exécution est déléguée au middleware redux-saga qui se charge d'exécuter la fonction et de reprendre le générateur avec son résultat.Le principal avantage est que vous pouvez tester le générateur en dehors de Redux à l'aide de simples vérifications d'égalité
Notez que nous nous moquons du résultat de l'appel api en injectant simplement les données simulées dans la
next
méthode de l'itérateur. Les données de simulation sont bien plus simples que les fonctions de simulation.La deuxième chose à noter est l'appel à
yield take(ACTION)
. Les thunks sont appelés par le créateur de l'action à chaque nouvelle action (par exempleLOGIN_REQUEST
). c'est-à-dire que les actions sont continuellement poussées vers les thunks, et les thunks n'ont aucun contrôle sur le moment d'arrêter de gérer ces actions.Dans redux-saga, les générateurs tirent la prochaine action. c'est-à-dire qu'ils ont le contrôle quand écouter une action, et quand ne pas. Dans l'exemple ci-dessus, les instructions de flux sont placées dans une
while(true)
boucle, donc il écoutera chaque action entrante, ce qui imite quelque peu le comportement de poussée du thunk.L'approche pull permet de mettre en œuvre des flux de contrôle complexes. Supposons par exemple que nous voulons ajouter les exigences suivantes
Gérer l'action utilisateur LOGOUT
lors de la première connexion réussie, le serveur renvoie un jeton qui expire dans un certain délai stocké dans un
expires_in
champ. Nous devrons actualiser l'autorisation en arrière-plan à chaqueexpires_in
millisecondeTenez compte du fait qu'en attendant le résultat des appels api (soit la connexion initiale, soit l'actualisation), l'utilisateur peut se déconnecter entre les deux.
Comment implémenteriez-vous cela avec des thunks; tout en offrant une couverture de test complète pour l'ensemble du flux? Voici à quoi cela peut ressembler avec les Sagas:
Dans l'exemple ci-dessus, nous exprimons notre exigence de concurrence à l'aide de
race
. Sitake(LOGOUT)
gagne la course (c'est-à-dire que l'utilisateur a cliqué sur un bouton de déconnexion). La course annulera automatiquement laauthAndRefreshTokenOnExpiry
tâche d'arrière - plan. Et si le aauthAndRefreshTokenOnExpiry
été bloqué au milieu d'uncall(authorize, {token})
appel, il sera également annulé. L'annulation se propage automatiquement vers le bas.Vous pouvez trouver une démo exécutable du flux ci-dessus
la source
delay
fonction? Ah, je l' airedux-thunk
code est assez lisible et s'explique de lui-même. Maisredux-sagas
on est vraiment illisible, principalement à cause de ceux-verbe comme des fonctions:call
,fork
,take
,put
...J'ajouterai mon expérience de l'utilisation de la saga dans le système de production en plus de la réponse plutôt approfondie de l'auteur de la bibliothèque.
Pro (en utilisant la saga):
Testabilité. Il est très facile de tester des sagas car call () renvoie un objet pur. Pour tester les thunks, vous devez normalement inclure un mockStore dans votre test.
redux-saga est livré avec de nombreuses fonctions d'aide utiles sur les tâches. Il me semble que le concept de saga est de créer une sorte de travailleur / thread d'arrière-plan pour votre application, qui agit comme une pièce manquante dans l'architecture React Redux (les créateurs d'action et les réducteurs doivent être des fonctions pures.) Ce qui nous amène au point suivant.
Les sagas offrent un lieu indépendant pour gérer tous les effets secondaires. Il est généralement plus facile de modifier et de gérer que les actions de thunk selon mon expérience.
Con:
Syntaxe du générateur.
Beaucoup de concepts à apprendre.
Stabilité de l'API. Il semble que redux-saga ajoute encore des fonctionnalités (par exemple des chaînes?) Et la communauté n'est pas aussi grande. Il y a un problème si la bibliothèque effectue un jour une mise à jour non rétrocompatible.
la source
API stability
une mise à jour pour refléter la situation actuelle.Je voudrais juste ajouter quelques commentaires de mon expérience personnelle (en utilisant à la fois des sagas et du thunk):
Les sagas sont super à tester:
Les sagas sont plus puissantes. Tout ce que vous pouvez faire dans le créateur d'action d'un thunk, vous pouvez également le faire dans une saga, mais pas l'inverse (ou du moins pas facilement). Par exemple:
take
)cancel
,takeLatest
,race
)take
,takeEvery
, ...)Sagas propose également d'autres fonctionnalités utiles, qui généralisent certains modèles d'application courants:
channels
pour écouter sur des sources d'événements externes (par exemple, websockets)fork
,spawn
)Les sagas sont un outil formidable et puissant. Mais avec le pouvoir vient la responsabilité. Lorsque votre application se développe, vous pouvez facilement vous perdre en déterminant qui attend que l'action soit distribuée ou ce qui se passe quand une action est distribuée. D'un autre côté, le thunk est plus simple et plus facile à raisonner. Le choix de l'un ou l'autre dépend de nombreux aspects tels que le type et la taille du projet, les types d'effets secondaires que votre projet doit gérer ou les préférences de l'équipe de développement. Dans tous les cas, gardez simplement votre application simple et prévisible.
la source
Juste une expérience personnelle:
Pour le style de codage et la lisibilité, l'un des avantages les plus importants de l'utilisation de redux-saga dans le passé est d'éviter l'enfer de rappel dans redux-thunk - on n'a plus besoin d'utiliser beaucoup d'imbrication then / catch. Mais maintenant, avec la popularité de l'async / wait thunk, on pourrait également écrire du code async dans le style de synchronisation lors de l'utilisation de redux-thunk, ce qui peut être considéré comme une amélioration de redux-think.
Il peut être nécessaire d'écrire beaucoup plus de code passe-partout lors de l'utilisation de redux-saga, en particulier dans Typescript. Par exemple, si l'on veut implémenter une fonction d'extraction asynchrone, la gestion des données et des erreurs pourrait être effectuée directement dans une unité thunk dans action.js avec une seule action FETCH. Mais dans redux-saga, il peut être nécessaire de définir les actions FETCH_START, FETCH_SUCCESS et FETCH_FAILURE et toutes leurs vérifications de type associées, car l'une des fonctionnalités de redux-saga est d'utiliser ce type de mécanisme riche en «jetons» pour créer des effets et instruire magasin redux pour des tests faciles. Bien sûr, on pourrait écrire une saga sans utiliser ces actions, mais cela la rendrait semblable à un thunk.
En termes de structure de fichiers, redux-saga semble être plus explicite dans de nombreux cas. On pourrait facilement trouver un code lié à async dans chaque sagas.ts, mais dans redux-thunk, il faudrait le voir dans les actions.
Les tests faciles peuvent être une autre fonctionnalité pondérée dans redux-saga. C'est vraiment pratique. Mais une chose qui doit être clarifiée est que le test "d'appel" de redux-saga n'effectuerait pas d'appel API réel lors des tests, donc il faudrait spécifier l'exemple de résultat pour les étapes qui peuvent l'utiliser après l'appel API. Par conséquent, avant d'écrire dans redux-saga, il serait préférable de planifier une saga et ses sagas.spec.ts correspondantes en détail.
Redux-saga fournit également de nombreuses fonctionnalités avancées telles que l'exécution de tâches en parallèle, des assistants de concurrence comme takeLatest / takeEvery, fork / spawn, qui sont beaucoup plus puissants que les thunks.
En conclusion, personnellement, je voudrais dire: dans de nombreux cas normaux et des applications de petite à moyenne taille, optez pour le style asynchrone / wait redux-thunk. Cela vous permettrait d'économiser de nombreux codes / actions / typedefs standard, et vous n'auriez pas besoin de basculer entre de nombreux sagas.ts différents et de maintenir un arbre de sagas spécifique. Mais si vous développez une grande application avec une logique asynchrone très complexe et le besoin de fonctionnalités telles que le modèle simultané / parallèle, ou si vous avez une forte demande de tests et de maintenance (en particulier dans le développement piloté par les tests), redux-sagas pourrait peut-être vous sauver la vie .
Quoi qu'il en soit, redux-saga n'est pas plus difficile et complexe que redux lui-même, et il n'a pas de courbe d'apprentissage dite abrupte car il a des concepts de base et des API bien limités. Passer un peu de temps à apprendre la redux-saga pourrait vous être utile un jour à l'avenir.
la source
Après avoir passé en revue plusieurs projets React / Redux à grande échelle dans mon expérience, Sagas offre aux développeurs une manière plus structurée d'écrire du code qui est beaucoup plus facile à tester et plus difficile à se tromper.
Oui, c'est un peu bizarre pour commencer, mais la plupart des développeurs en comprennent assez en une journée. Je dis toujours aux gens de ne pas se soucier de ce
yield
qui commence et qu'une fois que vous aurez passé quelques tests, cela vous reviendra.J'ai vu quelques projets où les thunks ont été traités comme s'ils étaient des contrôleurs du patten MVC et cela devient rapidement un gâchis incontrôlable.
Mon conseil est d'utiliser des Sagas où vous avez besoin de déclencheurs A de type B relatifs à un seul événement. Pour tout ce qui pourrait recouper un certain nombre d'actions, je trouve qu'il est plus simple d'écrire le middleware client et d'utiliser la méta-propriété d'une action FSA pour la déclencher.
la source
Thunks contre Sagas
Redux-Thunk
etRedux-Saga
diffèrent de plusieurs manières importantes, les deux sont des bibliothèques de middleware pour Redux (le middleware Redux est un code qui intercepte les actions entrant dans le magasin via la méthode dispatch ()).Une action peut être n'importe quoi, mais si vous suivez les meilleures pratiques, une action est un simple objet javascript avec un champ de type et des champs facultatifs de charge utile, de méta et d'erreur. par exemple
Redux-Thunk
En plus de distribuer des actions standard, le
Redux-Thunk
middleware vous permet de distribuer des fonctions spéciales, appeléesthunks
.Les Thunks (dans Redux) ont généralement la structure suivante:
Autrement dit, a
thunk
est une fonction qui (facultativement) prend certains paramètres et renvoie une autre fonction. La fonction interne prend une fonctiondispatch function
et unegetState
fonction, toutes deux fournies par leRedux-Thunk
middleware.Redux-Saga
Redux-Saga
le middleware vous permet d'exprimer une logique d'application complexe sous forme de fonctions pures appelées sagas. Les fonctions pures sont souhaitables du point de vue des tests car elles sont prévisibles et répétables, ce qui les rend relativement faciles à tester.Les sagas sont implémentées via des fonctions spéciales appelées fonctions génératrices. Ce sont une nouvelle fonctionnalité de
ES6 JavaScript
. Fondamentalement, l'exécution saute dans et hors d'un générateur partout où vous voyez une déclaration de rendement. Considérez uneyield
déclaration comme provoquant une pause du générateur et renvoyant la valeur produite. Plus tard, l'appelant peut reprendre le générateur à l'instruction suivant leyield
.Une fonction de générateur est définie comme celle-ci. Remarquez l'astérisque après le mot-clé function.
Une fois la saga de connexion enregistrée
Redux-Saga
. Mais ensuite, layield
prise sur la première ligne mettra la saga en pause jusqu'à ce qu'une action de type'LOGIN_REQUEST'
soit envoyée au magasin. Une fois que cela se produit, l'exécution se poursuit.Pour plus de détails, consultez cet article .
la source
Une petite note. Les générateurs sont annulables, asynchrones / attendent - pas. Donc, pour un exemple de la question, cela n'a pas vraiment de sens de quoi choisir. Mais pour des flux plus compliqués, il n'y a parfois pas de meilleure solution que d'utiliser des générateurs.
Donc, une autre idée pourrait être d'utiliser des générateurs à redux-thunk, mais pour moi, cela ressemble à essayer d'inventer un vélo à roues carrées.
Et bien sûr, les générateurs sont plus faciles à tester.
la source
Voici un projet qui combine les meilleures parties (pros) des deux
redux-saga
etredux-thunk
: vous pouvez gérer tous les effets secondaires sur les sagas tout en obtenant une promesse pardispatching
l'action correspondante: https://github.com/diegohaz/redux-saga-thunkla source
then()
intérieur d'un composant React est contraire au paradigme. Vous devez gérer l'état modifiécomponentDidUpdate
plutôt que d'attendre qu'une promesse soit résolue.componentDidlMount() { this.props.doSomething().then((detail) => { this.setState({isReady: true})} }
Un moyen plus simple consiste à utiliser redux-auto .
de la documantasion
L'idée est d'avoir chaque action dans un fichier spécifique . colocaliser l'appel du serveur dans le fichier avec les fonctions de réduction pour "en attente", "satisfait" et "rejeté". Cela rend la gestion des promesses très facile.
Il attache également automatiquement un objet d'assistance (appelé "async") au prototype de votre état, vous permettant de suivre dans votre interface utilisateur les transitions demandées.
la source