Avantages/inconvénients de l'utilisation de redux-saga avec ES6 générateurs vs redux-thunk avec ES2017 async/await
Il y a beaucoup de discussion à propos du dernier enfant dans redux ville, redux-saga/redux-saga. Il utilise le générateur de fonctions pour écouter/envoi actions.
Avant de m'envelopper ma tête autour de lui, j'aimerais connaître les avantages/inconvénients de l'utilisation de redux-saga
au lieu de l'approche ci-dessous où je suis en utilisant redux-thunk
avec async/await.
Un composant pourrait ressembler à ceci, envoi 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);
Alors mes actes ressembler à quelque chose comme 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...
- Voir aussi ma réponse en comparant redux-thunk pour redux-saga ici: stackoverflow.com/a/34623840/82609
- Qu'est-ce que le
::
avant votrethis.onClick
faire? - c'est un court de main pour la liaison de la fonction de l'objet (
this
), akathis.onClick = this.onClick.bind(this)
. La forme longue est généralement recommandé de le faire dans le constructeur, comme le court de main re-lie sur chaque rendu. - Je vois. merci! Je vois des gens à l'aide d'
bind()
beaucoup à passerthis
à la fonction, mais j'ai commencé à utiliser() => method()
maintenant. - avez-vous trouvé redux saga utile? Après l'explication, que je ne pouvais pas trouver n'importe quel avantage.
- J'ai utilisé redux & redux-saga de la production pendant un certain temps, mais effectivement migré vers MobX après une couple de mois car moins de frais généraux
Vous devez vous connecter pour publier un commentaire.
Dans redux-saga, l'équivalent de l'exemple ci-dessus serait
La première chose à remarquer est que nous allons appeler les fonctions de l'api à l'aide du formulaire
yield call(func, ...args)
.call
n'exécute pas l'effet, il crée un objet ordinaire comme{type: 'CALL', func, args}
. L'exécution est déléguée à la redux-saga middleware qui prend soin de l'exécution de la fonction et de reprendre le générateur de son résultat.Le principal avantage est que vous pouvez tester le générateur à l'extérieur de Redux à l'aide de simples contrôles d'égalité
Noter que nous sommes se moquant de l'api résultat de l'appel par simple injection de la moqué de données dans le
next
méthode de l'itérateur. Se moquant de données est plus simple que de se moquant de fonctions.La deuxième chose à remarquer, c'est l'appel à
yield take(ACTION)
. Les Thunks sont appelés par l'action du créateur sur chaque nouvelle action (par exemple,LOGIN_REQUEST
). c'est à dire les actions sont continuellement poussé pour les thunks, et les thunks n'avons aucun contrôle sur le moment de cesser le traitement de ces actions.Dans redux-saga, générateurs pull de la prochaine action. c'est à dire qu'ils ont le contrôle lors de l'écouter un peu d'action, et quand ne pas. Dans l'exemple ci-dessus, le flux d'instructions sont placées à l'intérieur d'un
while(true)
boucle, donc ça va écouter pour chaque nouvelle action, qui imite un peu le thunk poussant comportement.L'approche "pull" permet de mettre en œuvre complexe des flux de contrôle. Supposons, par exemple, nous voulons ajouter les exigences suivantes
Poignée de DÉCONNEXION de l'utilisateur action
lors de la première connexion réussie, le serveur renvoie un jeton qui expire dans un peu de retard stockées dans un
expires_in
champ. Nous allons actualiser l'autorisation en arrière-plan sur chaqueexpires_in
millisecondesPrendre en compte que lors de l'attente pour le résultat des appels d'api (soit votre première connexion ou actualiser) l'utilisateur peut déconnexion entre les deux.
Comment voulez-vous mettre en œuvre qu'avec les thunks; tout en fournissant de l'essai complet de la couverture de l'ensemble des flux? Voici comment il peut regarder avec les Sagas:
Dans l'exemple ci-dessus, nous sommes en exprimant notre exigence de simultanéité à l'aide de
race
. Sitake(LOGOUT)
gagne la course (c'est à dire l'utilisateur a cliqué sur un Bouton de Déconnexion). La course entraînera automatiquement l'annulation de laauthAndRefreshTokenOnExpiry
tâche de fond. Et si leauthAndRefreshTokenOnExpiry
a été bloqué au milieu d'uncall(authorize, {token})
appeler ça va être annulées. L'annulation se propage à la baisse automatiquement.Vous pouvez trouver un praticable de démonstration de la au-dessus de flux
delay
fonction en venir? Ah, trouvé: github.com/yelouafi/redux-saga/blob/...redux-thunk
code est lisible et auto-expliqué. Maisredux-sagas
est vraiment illisible, principalement à cause de ces verbes comme des fonctions:call
,fork
,take
,put
...Je vais ajouter mon expérience à l'aide de la saga dans le système de production en plus de la bibliothèque de l'auteur plutôt approfondie réponse.
Pro (à l'aide de la saga):
La testabilité. Il est très facile de tester sagas comme call() retourne un pur objet. Les tests de thunks normalement vous oblige à inclure un mockStore à l'intérieur de votre test.
redux-saga est livré avec de nombreuses fonctions d'assistance sur les tâches. Il me semble que le concept de la saga est de créer une sorte d'arrière-plan travailleur/thread pour votre application, qui agissent comme une pièce manquante à réagir redux architecture(actionCreators et les réducteurs doivent être des fonctions pures.) Ce qui conduit au point suivant.
Sagas offre indépendante pour gérer tous les effets secondaires. Il est généralement plus facile à modifier et à gérer qu'un thunk des actions dans mon expérience.
Con:
Générateur de syntaxe.
Beaucoup de concepts à apprendre.
API de stabilité. Il semble redux-saga est encore l'ajout de fonctionnalités (par exemple, les Canaux?) et la communauté n'est pas aussi grand. Il y a un problème si la bibliothèque non compatible mise à jour de quelques jours.
API stability
comme une mise à jour pour refléter la situation actuelle.Je voudrais juste ajouter quelques commentaires à partir de mon expérience personnelle (à l'aide de deux sagas et de thunk):
Sagas sont parfaits pour tester:
Sagas sont de plus en plus puissants. Tout ce que vous pouvez faire dans un thunk d'action du créateur, vous pouvez aussi le faire dans une saga, mais pas vice-versa (ou du moins pas facilement). Par exemple:
take
)cancel
,takeLatest
,race
)take
,takeEvery
, ...)Sagas offre également d'autres fonctions utiles, qui généraliser certaines communes schémas d'application:
channels
pour écouter des sources (par exemple, les websockets)fork
,spawn
)Sagas sont grand et puissant outil. Cependant, avec le pouvoir vient la responsabilité. Lorsque votre application grandit, vous pouvez facilement se perdre en cherchant qui est en attente pour l'action à être distribué, ou ce que tout se passe lorsqu'une action est distribué. D'autre part thunk est plus simple et plus facile à comprendre. Le choix de l'un ou l'autre dépend de nombreux aspects comme le type et la taille du projet, quels sont les types d'effet secondaire à votre projet doit gérer ou de l'équipe de dev de préférence. En tout cas, il suffit de garder votre application simple et prévisible.
Juste une expérience personnelle:
Pour le style de codage et de lisibilité, l'un des plus importants avantages de l'utilisation de redux-saga dans le passé, c'est d'éviter de rappel de l'enfer dans redux-thunk — on n'a pas besoin d'utiliser beaucoup de nidification puis/catch plus. Mais maintenant, avec la popularité de async/await thunk, on pourrait aussi écrire du code asynchrone synchronisé de style lors de l'utilisation de redux-thunk, ce qui peut être considéré comme une amélioration dans redux-penser.
On peut avoir besoin d'écrire beaucoup plus de code réutilisable lors de l'utilisation de redux-saga, surtout en caractères d'imprimerie. Par exemple, si l'on veut mettre en œuvre une extraction asynchrone en fonction, les données et les erreurs de manipulation peut être effectuée directement dans un thunk unité action.js avec une seule EXTRACTION de l'action. Mais dans redux-saga, on peut avoir besoin de définir FETCH_START, FETCH_SUCCESS et FETCH_FAILURE actions et leurs type de vérifications, parce que l'une des caractéristiques de redux-la saga de l'utilisation de ce type de riches “jeton” mécanisme pour créer des effets et instruire redux magasin pour tester facilement. Bien sûr, on pourrait écrire une saga sans l'aide de ces actions, mais que ferait-il de même pour un thunk.
En termes de structure de fichier, redux-saga semble être plus explicite dans de nombreux cas. On pourrait facilement trouver un async le code associé à tous les sagas.ts, mais dans redux-thunk, on aurait besoin de le voir en action.
Facile de test peut être un autre pondéré en fonction redux-saga. C'est vraiment pratique. Mais une chose qui doit être précisé, c'est que redux-saga “call” test ne serait pas effectuer une véritable appel d'API dans les essais, donc, il serait nécessaire de spécifier le résultat d'échantillonnage pour les mesures qui peuvent l'utiliser après l'appel d'API. Donc avant d'écrire dans redux-saga, il serait préférable de prévoir une saga et de ses sagas.spec.ts en détail.
Redux-saga offre également de nombreuses fonctionnalités avancées, telles que l'exécution de tâches en parallèle, la simultanéité aides comme takeLatest/takeEvery, fourche/spawn, qui sont beaucoup plus puissantes que les thunks.
En conclusion, personnellement, je tiens à le dire: dans de nombreux cas normaux et les petites et moyennes applications, aller avec async/await style redux-thunk. Il serait vous faire économiser beaucoup de passe-partout de codes/actions/typedefs, et vous n'auriez pas besoin de passer autour de plusieurs sagas.ts et assurer le maintien de la sagas de l'arbre. Mais si vous développez un grand app avec beaucoup plus complexe asynchrone de la logique et de la nécessité pour les fonctions comme la simultanéité/modèle parallèle, ou ont une forte demande pour les essais et l'entretien (en particulier dans le développement piloté par les tests), redux-sagas serait peut-être sauver votre vie.
De toute façon, redux-saga n'est pas plus difficile et complexe que redux lui-même, et il ne dispose pas d'un soi-disant courbe d'apprentissage abrupte, car il a bien limité les concepts de base et les Api. Passer une petite quantité de temps d'apprentissage redux-saga peuvent bénéficier vous-même un jour dans l'avenir.
Thunks contre les Sagas
Redux-Thunk
etRedux-Saga
diffèrent dans quelques-uns des moyens importants, à la fois des librairies middleware pour Redux (Redux middleware est le code qui intercepte les actions à venir dans le magasin via la méthode dispatch ()).Une action peut être littéralement n'importe quoi, mais si vous êtes en suivant les meilleures pratiques, une action est un simple objet javascript avec un champ de type a, et en option de la charge utile, méta, d'erreur et de champs. par exemple,
Redux-Thunk
En outre l'envoi d'actions standard,
Redux-Thunk
middleware permet d'envoi des fonctions spéciales, appeléesthunks
.Thunks (dans Redux) ont généralement la structure suivante:
Qui est, un
thunk
est une fonction (en option) prend un peu de paramètres et renvoie une autre fonction. La fonction interne prend unedispatch function
et ungetState
fonction -- qui sera fourni par leRedux-Thunk
middleware.Redux-Saga
Redux-Saga
middleware permet de vous exprimer complexe de l'application de la logique pure fonctions appelées sagas. Les fonctions pures sont souhaitables à partir d'un test de point de vue parce qu'ils sont prévisibles et reproductibles, ce qui les rend relativement facile à tester.Sagas sont mis en œuvre grâce à des fonctions spéciales appelées générateur de fonctions. 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 instruction rendement. Pensez à unyield
déclaration comme causant le générateur de pause et retour le donné de la valeur. Plus tard, l'appelant peut reprendre le générateur à l'instruction suivant l'yield
.Un générateur de fonction est définie comme ceci. Avis de l'astérisque après le mot-clé function.
Une fois la connexion saga est enregistré avec
Redux-Saga
. Mais alors leyield
prendre sur la la première ligne de pause de la saga jusqu'à ce qu'une action de type'LOGIN_REQUEST'
est envoyé à la banque. Une fois que cela arrive, l'exécution se poursuivra.Pour plus de détails, voir cet article.
Voici un projet qui combine les meilleurs éléments (pros) de 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-thunk
then()
à l'intérieur d'une Réagir composant est contre le paradigme. Vous devez gérer le changement de l'état danscomponentDidUpdate
plutôt que d'attendre une promesse d'être résolu.componentDidlMount() { this.props.doSomething().then((detail) => { this.setState({isReady: true})} }
Un moyen plus facile est d'utiliser redux-auto.
de la documantasion
L'idée est d'avoir chaque d'action dans un fichier spécifique. la co-localisation de l'appel de serveur dans le fichier avec réducteur de fonctions pour "en attente", "satisfaits" et "rejeté". Cela rend la manipulation des promesses très facile.
Il a également joint automatiquement une helper object(appelé "async") pour le prototype de votre état, vous permettant de suivre dans votre INTERFACE utilisateur, demandé des transitions.
Ayant passé en revue les différentes grande échelle Réagir/Redux projets dans mon expérience, les Sagas de fournir aux développeurs une façon plus structurée de l'écriture de code, qui est beaucoup plus facile à tester et plus difficile de se tromper.
Oui c'est un peu bizarre au début, mais la plupart des devs obtenir assez de la compréhension d'une journée. Je dis toujours aux gens de ne pas vous soucier de ce que
yield
n'dès le départ, et qu'une fois que vous écrivez un couple de test, il viendra à vous.J'ai vu un couple de projets où les thunks ont été traités comme s'ils sont les contrôleurs de la MVC patten et cela devient rapidement un unmaintable mess.
Mon conseil est d'utiliser des Sagas où vous avez besoin d'Un des déclencheurs de type B trucs relatifs à un événement unique. Pour tout ce qui peut couper à travers un certain nombre d'actions, je trouve que c'est plus simple à écrire client middleware et de l'utilisation de la méta-propriété d'un FSA d'action pour la déclencher.
Une note rapide. Les générateurs sont annulables, async/await — pas.
Donc, pour un exemple de la question, il n'a pas vraiment le sens de ce qu'il faut chercher.
Mais pour plus compliquée flux parfois, il n'y a pas de meilleure solution que d'utiliser des générateurs.
Une autre idée est d'utiliser des générateurs de redux-thunk, mais pour moi, il semble être en train d'inventer un vélo avec des carrés des roues.
Et bien sûr, les générateurs sont plus faciles à tester.