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
- DOM : jQuery est impératif / React est déclaratif
- Monades : IO est impératif / Free est déclaratif
- Effets redux :
redux-thunk
est impératif / redux-saga
est déclaratif
Lorsque 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.
Vu à travers la lentille des effets secondaires, les simulacres sont un indicateur que votre code est impur, et aux yeux du programmeur fonctionnel, la preuve que quelque chose ne va pas. Au lieu de télécharger une bibliothèque pour nous aider à vérifier que l'iceberg est intact, nous devrions le contourner. Un gars hardcore TDD / Java m'a demandé une fois comment vous vous moquiez de Clojure. La réponse est que ce n'est généralement pas le cas. Nous le voyons généralement comme un signe dont nous avons besoin pour refactoriser notre code.
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 :
dans la PF moderne, nous ne devons pas écrire de programmes - nous devons écrire des descriptions de programmes, que nous pouvons ensuite introspecter, transformer et interpréter à volonté.
(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
<div onClick={e => dispatch(actions.loadUserProfile(123)}>Robert</div>
function loadUserProfile(userId) {
return dispatch => fetch(`http://data.com/${userId}`)
.then(res => res.json())
.then(
data => dispatch({ type: 'USER_PROFILE_LOADED', data }),
err => dispatch({ type: 'USER_PROFILE_LOAD_FAILED', err })
);
}
redux-saga
<div onClick={e => dispatch({ type: 'USER_NAME_CLICKED', payload: 123 })}>Robert</div>
function* loadUserProfileOnNameClick() {
yield* takeLatest("USER_NAME_CLICKED", fetchUser);
}
function* fetchUser(action) {
try {
const userProfile = yield fetch(`http://data.com/${action.payload.userId }`)
yield put({ type: 'USER_PROFILE_LOADED', userProfile })
}
catch(err) {
yield put({ type: 'USER_PROFILE_LOAD_FAILED', err })
}
}
Cette saga se traduit par:
chaque fois qu'un nom d'utilisateur est cliqué, récupérez le profil utilisateur, puis envoyez un événement avec le profil chargé.
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 utiliser takeEvery
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 composants dispatch
), 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 parchemin
Certains 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.
const loadUserProfileOnNameClickEpic = action$ =>
action$.ofType('USER_NAME_CLICKED')
.switchMap(action =>
Observable.ajax(`http://data.com/${action.payload.userId}`)
.map(userProfile => ({
type: 'USER_PROFILE_LOADED',
userProfile
}))
.catch(err => Observable.of({
type: 'USER_PROFILE_LOAD_FAILED',
err
}))
);
Quelques ressources utiles de redux-saga
Conseils 2017
- Ne pas abuser de Redux-saga juste pour le plaisir de l'utiliser. Seuls les appels API testables n'en valent pas la peine.
- Ne supprimez pas les thunks de votre projet pour la plupart des cas simples.
- N'hésitez pas à envoyer des thunks
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.
const unsubscribe = store.addDispatchListener(action => {
if (action.type === 'ping') {
store.dispatch({ type: 'pong' });
}
});