Mrr est une bibliothèque fonctionnelle-réactive pour React (je m'excuse pour la tautologie imaginaire).
Le mot «réactivité»
fait généralement
référence à Rx.js comme FRP de référence. Cependant, une série d'articles récents sur ce sujet sur Habré (
[1] ,
[2] ,
[3] ) a montré la lourdeur des solutions sur Rx, qui dans des exemples simples ont perdu en clarté et en simplicité à presque toute autre approche. Rx est grand et puissant, et est parfait pour résoudre des problèmes dans lesquels l'abstraction du flux se suggère (en pratique, c'est principalement la coordination de tâches asynchrones). Mais écririez-vous, par exemple, une simple validation de formulaire synchrone sur Rx? Pourrait-il vous faire gagner du temps par rapport aux approches impératives habituelles?
mrr est une tentative de prouver que FRF peut être une solution pratique et efficace non seulement dans des problèmes spécifiques de "streaming", mais aussi dans les tâches frontales de routine les plus courantes.
La programmation réactive est une abstraction très puissante, pour le moment en front-end elle est présente de deux manières:
- Variables réactives (variables calculées): simples, fiables, intuitives, mais le potentiel du RP est loin d'être pleinement révélé
- bibliothèques pour travailler avec des flux, tels que Rx, Bacon, etc.: puissantes, mais plutôt compliquées, la portée de l'utilisation pratique est limitée à des tâches spécifiques.
Mrr combine les avantages de ces approches. Contrairement à Rx.js, mrr dispose d'une API courte, que l'utilisateur peut développer avec ses ajouts. Au lieu de dizaines de méthodes et d'opérateurs - quatre opérateurs de base, au lieu d'Observable (chaud et froid), Sujet, etc. - une abstraction: flux. De plus, mrr manque de concepts complexes qui peuvent compliquer considérablement la lisibilité du code, par exemple, les métastream.
Cependant, mrr n'est pas un «Rx simplifié d'une manière nouvelle». Basé sur les mêmes principes de base que Rx, mrr prétend être un créneau plus important: gérer l'état global et local (au niveau des composants) de l'application. Bien que le concept original de programmation réactive ait été conçu pour fonctionner avec des tâches asynchrones, mrr a utilisé avec succès des approches de réactivité pour des tâches synchrones ordinaires. C'est le principe du «FRP total».
Souvent, lors de la création d'une application sur React, plusieurs technologies différentes sont utilisées: recomposition (ou bientôt - hooks) pour l'état du composant, Redux / mobx pour l'état global, Rx avec redux-observable (ou thunk / saga) pour la gestion des effets secondaires et la coordination asynchrone tâches dans l'éditeur. Au lieu d'une telle «salade» d'approches et de technologies différentes au sein d'une même application, avec mrr, vous pouvez utiliser une seule technologie et un seul paradigme.
L'interface mrr est également significativement différente de Rx et des bibliothèques similaires - elle est plus déclarative. Grâce à l'abstraction de réactivité et à l'approche déclarative, mrr vous permet d'écrire du code expressif et concis. Par exemple, le
TodoMVC standard sur mrr prend moins de 50 lignes de code (sans compter le modèle JSX).
Mais assez de publicité. Avez-vous réussi à combiner les avantages du RP "léger" et "lourd" dans une bouteille - vous devriez en juger, mais d'abord, veuillez lire les exemples de code.
TodoMVC est déjà assez douloureux, et l'exemple de téléchargement de données sur les utilisateurs de Github est trop primitif pour pouvoir ressentir les fonctionnalités de la bibliothèque dessus. Nous considérerons mrr comme un exemple d'application conditionnelle pour l'achat de billets de train. Dans notre interface utilisateur, il y aura des champs pour choisir les stations de départ et d'arrivée, les dates. Ensuite, après l'envoi des données, une liste des trains et des places disponibles sera retournée. Après avoir sélectionné un train et un type de voiture spécifiques, l'utilisateur saisira les données des passagers, puis ajoutera les billets au panier. Allons-y.
Nous avons besoin d'un formulaire avec un choix de stations et de dates:

Créez des champs de saisie semi-automatique pour l'entrée des stations.
import { withMrr } from 'mrr'; const stations = [ '', '', '', ' ', ... ] const Tickets = withMrr({
Les composants mrr sont créés à l'aide de la fonction withMrr, qui accepte un diagramme de lien réactif (description des flux) et une fonction de rendu. Les fonctions de rendu sont passées aux accessoires du composant, ainsi qu'à l'état, qui est maintenant complètement contrôlé par mrr. Il contiendra l'initiale (bloc $ init) et calculé par les valeurs de formule des cellules réactives.
Nous avons maintenant deux cellules (ou deux flux, ce qui est la même chose):
stationFromInput , les valeurs auxquelles tombent les entrées utilisateur à l'aide de
$ helper (en passant par défaut
event.target.value pour les éléments d'entrée de données), et une cellule qui en dérive
stationFromOptions , contenant un tableau de stations correspondantes par nom.
La valeur de
stationFromOptions est automatiquement calculée chaque fois qu'une cellule parent est modifiée à l'aide d'une fonction (dans la terminologie mrr appelée «
formule » - par analogie avec les formules Excel). La syntaxe des expressions mrr est simple: en premier lieu est la fonction (ou opérateur) par laquelle la valeur de la cellule est calculée, puis il y a une liste de cellules dont dépend cette cellule: leurs valeurs sont transmises à la fonction. Une telle syntaxe étrange, à première vue, présente de nombreux avantages, que nous examinerons plus loin. Jusqu'à présent, la logique mrr rappelle ici l'approche habituelle avec les variables calculables utilisées dans Vue, Svelte et d'autres bibliothèques, à la seule différence que vous pouvez utiliser des fonctions pures.
Nous implémentons la substitution de la station sélectionnée dans la liste du champ de saisie. Il est également nécessaire de masquer la liste des stations après que l'utilisateur a cliqué sur l'une d'entre elles.
const Tickets = withMrr({ $init: { stationFromOptions: [], stationFromInput: '', }, stationFromOptions: [str => stations.filter(s => str.indexOf(a) === 0), 'stationFromInput'], stationFrom: ['merge', 'stationFromInput', 'selectStationFrom'], optionsShown: ['toggle', 'stationFromInput', 'selectStationFrom'], }, (state, props, $) => { return (<div> <div> : <input onChange={ $('stationFromInput') } value={ state.stationFrom }/> </div> { state.optionsShown && <ul className="stationFromOptions"> { state.stationFromOptions.map(s => <li onClick={ $('selectStationFrom', s) }>{ s }</li>) } </ul> } </div>); });
Un gestionnaire d'événements créé à l'aide de l'aide $ dans la liste des stations émettra des valeurs fixes pour chaque option.
mrr est cohérent dans son approche déclarative, étranger à toute mutation. Après avoir sélectionné une station, nous ne pouvons pas «forcer» à changer la valeur de la cellule. Au lieu de cela, nous créons une nouvelle cellule
stationFrom qui, à l'aide de l'opérateur de
fusion de fusion de flux (un équivalent approximatif sur Rx est combineLatest), collectera les valeurs de deux flux: entrée utilisateur (
stationFromInput ) et sélection de station (
selectStationFrom ).
Nous devons afficher la liste des options une fois que l'utilisateur a entré quelque chose et se cacher après avoir sélectionné l'une des options. La cellule
optionsShown sera responsable de la visibilité de la liste des options, qui prendra des valeurs booléennes en fonction des changements dans les autres cellules. Il s'agit d'un modèle très courant pour lequel il existe du sucre syntaxique - l'opérateur à
bascule . Il définit la valeur de la cellule sur
true lors de tout changement du premier argument (flux) et sur
false - le second.
Ajoutez un bouton pour effacer le texte saisi.
const Tickets = withMrr({ $init: { stationFromOptions: [], stationFromInput: '', }, stationFromOptions: [str => stations.filter(s => str.indexOf(a) === 0), 'stationFromInput'], clearVal: [a => '', 'clear'], stationFrom: ['merge', 'stationFromInput', 'selectStationFrom', 'clearVal'], optionsShown: ['toggle', 'stationFromInput', 'selectStationFrom'], }, (state, props, $) => { return (<div> <div> : <input onChange={ $('stationFromInput') } value={ state.stationFrom }/> { state.stationFrom && <button onClick={ $('clear') }></button> } </div> { state.optionsShown && <ul className="stationFromOptions"> { state.stationFromOptions.map(s => <li onClick={ $('selectStationFrom', s) }>{ s }</li>) } </ul> } </div>); });
Maintenant, notre cellule
stationFrom , qui est responsable du contenu du texte dans le champ de saisie, collecte ses valeurs non pas à partir de deux, mais à partir de trois flux. Ce code peut être simplifié. La construction mrr de la forme [* formule *, * ... arguments de cellule *] est similaire aux expressions S en Lisp, et comme en Lisp, vous pouvez imbriquer arbitrairement de telles constructions les unes dans les autres.
Débarrassons-nous de la cellule clearVal inutile et raccourcissons le code:
stationFrom: ['merge', 'stationFromInput', 'selectStationFrom', [a => '', 'clear']],
Les programmes écrits dans un style impératif peuvent être comparés à une équipe mal organisée, où tout le monde se commande constamment quelque chose (je fais allusion aux appels de méthode et aux changements de mutation), de plus, les deux managers sont des subordonnés et vice versa. Les programmes déclaratifs sont similaires à l'image utopique opposée: un collectif où chacun sait clairement comment il doit agir dans n'importe quelle situation. Il n'y a pas besoin de commandes dans une telle équipe, tout le monde est à sa place et travaille en réponse à ce qui se passe.
Au lieu de décrire toutes les conséquences possibles d'un événement (lire - pour effectuer certaines mutations), nous décrivons tous les cas dans lesquels cet événement peut se produire, c'est-à-dire quelle valeur la cellule prendra lors de tout changement dans d'autres cellules. Dans notre petit exemple jusqu'à présent, nous avons décrit la cellule stationFrom et trois situations qui affectent sa valeur. Pour un programmeur habitué au code impératif, une telle approche peut sembler inhabituelle (voire une «béquille», une «perversion»). En fait, cela vous permet d'économiser des efforts en raison de la brièveté (et de la stabilité) du code, comme nous le verrons dans la pratique.
Et l'asynchronie? Est-il possible de faire apparaître la liste des stations proposées avec ajax? Pas de problème! En substance, peu importe pour mrr que la fonction renvoie une valeur ou une promesse. Lorsque mrr est retourné, il attend sa résolution et «pousse» les données reçues dans le flux.
stationFromOptions: [str => fetch('/get_stations?str=' + str).then(res => res.toJSON()), 'stationFromInput'],
Cela signifie également que vous pouvez utiliser des fonctions asynchrones comme formules. Des cas plus complexes (gestion des erreurs, statut de la promesse) seront examinés ultérieurement.
La fonctionnalité de choix d'une gare de départ est prête. Cela n'a aucun sens de reproduire le même pour la gare d'arrivée, il vaut la peine de le mettre dans un composant séparé qui peut être réutilisé. Ce sera un composant d'entrée généralisé avec auto-complétion, nous allons donc renommer les champs et créer la fonction pour obtenir les options appropriées définies dans les accessoires.
const OptionsInput = withMrr(props => ({ $init: { options: [], }, val: ['merge', 'valInput', 'selectOption', [a => '', 'clear']], options: [props.getOptions, 'val'], optionsShown: ['toggle', 'valInput', 'selectOption'], }), (state, props, $) => <div> <div> <input onChange={ $('valInput') } value={ state.val } /> </div> { state.optionsShown && <ul className="options"> { state.options.map(s => <li onClick={ $('selectOption', s) }>{ s }</li>) } </ul> } { state.val && <div className="clear" onClick={ $('clear') }> X </div> } </div>)
Comme vous pouvez le voir, vous pouvez spécifier la structure des cellules mrr en fonction du composant props (cependant, elle ne sera exécutée qu'une seule fois - lors de l'initialisation, et ne répondra pas aux changements dans les props).
Communication entre composants
Connectez maintenant ce composant au composant parent et voyez comment mrr permet aux composants liés d'échanger des données.
const getMatchedStations = str => fetch('/get_stations?str=' + str).then(res => res.toJSON()); const Tickets = withMrr({ stationTo: 'selectStationFrom/val', stationFrom: 'selectStationTo/val', }, (state, props, $, connectAs) => { return (<div> <OptionsInput { ...connectAs('selectStationFrom') } getOptions={ getMatchedStations } /> - <OptionsInput { ...connectAs('selectStationTo') } getOptions={ getMatchedStations } /> <input type="date" onChange={ $('date') } /> <button onClick={ $('searchTrains') }></button> </div>); });
Pour associer un composant parent à un composant enfant, nous devons lui passer des paramètres à l'aide de la fonction
connectAs (quatrième argument de la fonction de rendu). Dans ce cas, nous indiquons le nom que nous voulons donner au composant enfant. En attachant un composant de cette manière, par ce nom, nous pouvons accéder à ses cellules. Dans ce cas, nous écoutons les cellules val. L'inverse est également possible - écoutez à partir du composant enfant de la cellule parent.
Comme vous pouvez le voir, ici mrr suit une approche déclarative: aucun rappel onChange n'est nécessaire, nous avons juste besoin de spécifier un nom pour le composant enfant dans la fonction connectAs, après quoi nous avons accès à ses cellules! Dans ce cas, toujours en raison de la déclarativité, il n'y a aucune menace d'interférence dans le travail d'un autre composant - nous n'avons pas la possibilité de «changer» quoi que ce soit, de muter, nous ne pouvons qu'écouter les données.
Signaux et valeurs
L'étape suivante est la recherche de trains adaptés aux paramètres sélectionnés. Dans l'approche impérative, nous écririons probablement un certain processeur pour soumettre le formulaire onSubmit, qui lancerait d'autres actions - une demande ajax et l'affichage des résultats. Mais, comme vous vous en souvenez, nous ne pouvons rien "commander"! Nous ne pouvons créer qu'un autre ensemble de cellules dérivées des cellules du formulaire. Écrivons une autre demande.
const getTrains = (from, to, date) => fetch('/get_trains?from=' + from + '&to=' + to + '&date=' + date).then(res => res.toJSON()); const Tickets = withMrr({ stationFrom: 'selectStationFrom/val', stationTo: 'selectStationTo/val', results: [getTrains, 'stationFrom', 'stationTo', 'date', 'searchTrains'], }, (state, props, $, connectAs) => { return (<div> <OptionsInput { ...connectAs('selectStationFrom') } getOptions={ getMatchedStations } /> - <OptionsInput { ...connectAs('selectStationTo') } getOptions={ getMatchedStations } /> <input type="date" onChange={ $('date') } /> <button onClick={ $('searchTrains') }></button> </div>); });
Cependant, un tel code ne fonctionnera pas comme prévu. Le calcul (recalcul de la valeur de la cellule) est lancé lorsque l'un des arguments change, de sorte que la demande sera envoyée, par exemple, immédiatement après avoir sélectionné la première station, et pas seulement en cliquant sur «Rechercher». Nous avons besoin, d'une part, que les stations et la date soient passées dans les arguments de la formule, mais d'autre part, ne répondent pas à leur changement. Dans mrr, il existe un mécanisme élégant pour cette écoute dite passive.
results: [getTrains, '-stationFrom', '-stationTo', '-date', 'searchTrains'],
Ajoutez juste un moins devant le nom de la cellule, et le tour est joué! Désormais, les
résultats ne répondront qu'aux modifications apportées à la cellule
searchTrains .
Dans ce cas, la cellule
searchTrains agit comme un «signal de cellule», et les cellules de
stationFrom et d'autres comme des «valeurs de cellule». Pour une cellule de signal, seul le moment où les valeurs «circulent» à travers elle est important, et quel type de données ce sera - de toute façon: cela peut être juste vrai, «1» ou autre (dans notre cas, ce seront des objets d'événement DOM ) Pour une cellule de valeur, c'est sa valeur qui est importante, mais en même temps, les moments de son changement ne sont pas significatifs. Ces deux types de cellules ne s'excluent pas mutuellement: de nombreuses cellules sont à la fois des signaux et des valeurs. Au niveau de la syntaxe dans mrr, ces deux types de cellules ne diffèrent en aucune façon, mais la compréhension conceptuelle d'une telle différence est très importante lors de l'écriture de code réactif.
Fractionnement des flux
Une demande de recherche de sièges dans le train peut prendre un certain temps, nous devons donc montrer le chargeur et également répondre en cas d'erreur. Il y a déjà peu de promesses pour cette approche par défaut avec résolution automatique.
const Tickets = withMrr({ $init: { results: {}, } stationFrom: 'selectStationFrom/val', stationTo: 'selectStationTo/val', searchQuery: [(from, to, date) => ({ from, to, date }), '-stationFrom', '-stationTo', '-date', 'searchTrains'], results: ['nested', (cb, query) => { cb({ loading: true, error: null, data: null }); getTrains(query.from, query.to, query.date) .then(res => cb('data', res)) .catch(err => cb('error', err)) .finally(() => cb('loading', false)) }, 'searchQuery'], availableTrains: 'results.data', }, (state, props, $, connectAs) => { return (<div> <div> <OptionsInput { ...connectAs('selectStationFrom') } getOptions={ getMatchedStations } /> - <OptionsInput { ...connectAs('selectStationTo') } getOptions={ getMatchedStations } /> <input type="date" onChange={ $('date') } /> <button onClick={ $('searchTrains') }></button> </div> <div> { state.results.loading && <div className="loading">...</div> } { state.results.error && <div className="error"> . , . .</div> } { state.availableTrains && <div className="results"> { state.availableTrains.map((train) => <div />) } </div> } </div> </div>); });
L'opérateur
imbriqué vous permet de «décomposer» les données en sous-cellules, pour cela, le premier argument de la formule est le rappel, avec lequel vous pouvez «pousser» les données dans la sous-cellule (une ou plusieurs). Nous avons maintenant des flux distincts qui sont responsables de l'erreur, de l'état de la promesse et des données reçues. L'opérateur imbriqué est un outil très puissant et l'un des rares impératifs de mrr (nous précisons nous-mêmes dans quelles cellules mettre les données). Alors que l'opérateur de fusion fusionne plusieurs threads en un seul, l'imbrication divise le thread en plusieurs sous-threads, ce qui est donc l'opposé.
L'exemple ci-dessus est une façon standard de travailler avec des promesses, dans mrr, il est généralisé en tant qu'opérateur de promesse et vous permet de raccourcir le code:
results: ['promise', (query) => getTrains(query.from, query.to, query.date), 'searchQuery'],
De plus, l'opérateur de promesse s'assure que seuls les résultats de la promesse la plus récente sont utilisés.

Composant pour afficher les sièges disponibles (pour plus de simplicité nous refuserons de différents types de voitures)
const TrainSeats = withMrr({ selectSeats: [(seatsNumber, { id }) => new Array(Number(seatsNumber)).fill(true).map(() => ({ trainId: id })), '-seatsNumber', '-$props', 'select'], seatsNumber: [() => 0, 'selectSeats'], }, (state, props, $) => <div className="train"> №{ props.num } { props.from } - { props.to }. : { props.seats || 0 } { props.seats && <div> : <input type="number" onChange={ $('seatsNumber') } value={ state.seatsNumber || 0 } max={ props.seats } /> <button onClick={ $('select') }></button> </div> } </div>);
Pour accéder aux accessoires dans une formule, vous pouvez vous abonner à la boîte spéciale $ props.
const Tickets = withMrr({ ... selectedSeats: '*/selectSeats', }, (state, props, $, connectAs) => { ... <div className="results"> { state.availableTrains.map((train, i) => <TrainSeats key={i} {...train} {...connectAs('train' + i)}/>) } </div> }
Nous utilisons à nouveau l'écoute passive pour capter le nombre de lieux sélectionnés lorsque vous cliquez sur le bouton "Sélectionner". Nous associons chaque composant enfant au parent à l'aide de la fonction connectAs. L'utilisateur peut sélectionner des sièges dans l'un des trains proposés, nous écoutons donc les changements dans tous les composants enfants en utilisant le masque "*".
Mais voici le problème: l'utilisateur peut ajouter des sièges d'abord dans un train, puis dans un autre, de sorte que les nouvelles données broyeront les précédentes. Comment «accumuler» des données de flux? Pour ce faire, il existe un opérateur de
fermeture qui, avec l'emboîtement et l'entonnoir, forme la base de mrr (tous les autres ne sont rien d'autre que du sucre syntaxique basé sur ces trois).
selectedSeats: ['closure', () => { let seats = [];
Lorsque vous utilisez la
fermeture en premier (sur componentDidMount), une fermeture est créée qui renvoie la formule. Elle a ainsi accès aux variables de fermeture. Cela vous permet de sauvegarder les données entre les appels de manière sûre - sans glisser dans l'abîme des variables globales et de l'état mutable partagé. Ainsi, la fermeture vous permet d'implémenter les fonctionnalités des opérateurs Rx tels que scan et autres. Cependant, cette méthode est bonne pour les cas difficiles. Si nous avons seulement besoin de sauvegarder la valeur d'une variable, nous pouvons simplement utiliser le lien vers la valeur précédente de la cellule en utilisant le nom spécial "^":
selectedSeats: [(seats, prev) => [...seats, ...prev], '*/selectSeats', '^']
L'utilisateur doit maintenant saisir un prénom et un nom pour chaque ticket sélectionné.
const SeatDetails = withMrr({}, (state, props, $) => { return (<div> { props.trainId } <input name="name" value={ props.name } onChange={ $('setDetails', e => ['name', e.target.value, props.i]) } /> <input name="surname" value={ props.surname } onChange={ $('setDetails', e => ['surname', e.target.value, props.i]) }/> <a href="#" onClick={ $('removeSeat', props.i) }>X</a> </div>); }) const Tickets = withMrr({ $init: { results: {}, selectedSeats: [], } stationFrom: 'selectStationFrom/val', stationTo: 'selectStationTo/val', searchQuery: [(from, to, date) => ({ from, to, date }), '-stationFrom', '-stationTo', '-date', 'searchTrains'], results: ['promise', (query) => getTrains(query.from, query.to, query.date), 'searchQuery'], availableTrains: 'results.data', selectedSeats: [(seats, prev) => [...seats, ...prev], '*/selectSeats', '^'] }, (state, props, $, connectAs) => { return (<div> <div> <OptionsInput { ...connectAs('selectStationFrom') } getOptions={ getMatchedStations } /> - <OptionsInput { ...connectAs('selectStationTo') } getOptions={ getMatchedStations } /> <input type="date" onChange={ $('date') } /> <button onClick={ $('searchTrains') }></button> </div> <div> { state.results.loading && <div className="loading">...</div> } { state.results.error && <div className="error"> . , . .</div> } { state.availableTrains && <div className="results"> { state.availableTrains.map((train, i) => <TrainSeats key={i} {...train} {...connectAs('train' + i)}/>) } </div> } { state.selectedSeats.map((seat, i) => <SeatDetails key={i} i={i} { ...seat } {...connectAs('seat' + i)}/>) } </div> </div>); });
La cellule
selectedSeats contient un tableau d'emplacements sélectionnés. Lorsque l'utilisateur saisit le nom et le prénom de chaque ticket, nous devons modifier les données dans les éléments correspondants du tableau.
selectedSeats: [(seats, details, prev) => {
L'approche standard ne nous convient pas: dans la formule, nous devons savoir quelle cellule a changé et réagir en conséquence. L'une des formes de l'opérateur de fusion nous aidera.
selectedSeats: ['merge', { '*/selectSeats': (seats, prev) => { return [...prev, ...seats]; }, '*/setDetails': ([field, value, i], prev) => { prev[i][field] = value; return prev; }, '*/removeSeat': (i, prev) => { prev.splice(i, 1); return prev; }, }, '^'],
C'est un peu comme les réducteurs Redux, mais avec une syntaxe plus flexible et plus puissante. Et vous ne pouvez pas avoir peur de muter le tableau, car seule la formule d'une cellule a le contrôle, respectivement, les modifications parallèles sont exclues (mais les tableaux mutants qui sont passés en arguments ne valent certainement pas la peine).
Collections réactives
Le modèle lorsque la cellule stocke et modifie la matrice est très courant. Toutes les opérations avec le tableau sont de trois types: insérer, modifier, supprimer. Pour décrire cela, il existe un élégant opérateur
coll . Utilisez-le pour simplifier le calcul des
sièges sélectionnés .
C'était:
selectedSeats: ['merge', { '*/selectSeats': (seats, prev) => { return [...prev, ...seats]; }, '*/setDetails': ([field, value, i], prev) => { prev[i][field] = value; return prev; }, '*/removeSeat': (i, prev) => { prev.splice(i, 1); return prev; }, 'addToCart': () => [], }, '^']
est devenu:
selectedSeats: ['coll', { create: '*/selectSeats', update: '*/setDetails', delete: ['merge', '*/removeSeat', [() => ({}), 'addToCart']] }]
cependant, le format des données dans le flux setDetails doit être légèrement modifié:
<input name="name" onChange={ $('setDetails', e => [{ name: e.target.value }, props.i]) } /> <input name="surname" onChange={ $('setDetails', e => [{ surname: e.target.value }, props.i]) }/>
En utilisant l'opérateur
coll , nous décrivons trois threads qui affecteront notre tableau. Dans ce cas, le flux de
création doit contenir les éléments eux-mêmes, qui doivent être ajoutés au tableau (généralement des objets). Le flux de
suppression accepte soit les index des éléments à supprimer (à la fois dans '* / removeSeat') que les masques. Le masque {} supprimera tous les éléments et, par exemple, le masque {nom: 'Carl'} supprimera tous les éléments portant le nom Carl. Le flux de
mise à jour accepte des paires de valeurs: le changement qui doit être fait avec l'élément (masque ou fonction), et l'index ou le masque des éléments qui doivent être modifiés. Par exemple, [{patronyme: 'Johnson'}, {}] définira le nom de famille Johnson sur tous les éléments du tableau.
L'opérateur coll utilise quelque chose comme un langage de requête interne, ce qui facilite le travail avec les collections et le rend plus déclaratif.
Le code complet de notre application dans JsFiddle.
Nous nous sommes familiarisés avec presque toutes les fonctionnalités de base nécessaires de mrr. Un sujet assez important qui est resté par-dessus bord est la gestion de patrimoine mondiale, qui pourra être discutée dans de futurs articles. Mais maintenant, vous pouvez commencer à utiliser mrr pour gérer l'état à l'intérieur d'un composant ou d'un groupe de composants associés.
Conclusions
Quelle est la puissance de mrr?
mrr vous permet d'écrire des applications dans React dans un style fonctionnel-réactif (mrr peut être déchiffré comme Make React Reactive). mrr est très expressif - vous passez moins de temps à écrire des lignes de code.
mrr fournit un petit ensemble d'abstractions de base, ce qui est suffisant pour tous les cas - cet article décrit presque toutes les principales caractéristiques et techniques de mrr.
Il existe également des outils pour étendre cet ensemble de base (la possibilité de créer des opérateurs personnalisés). Vous pouvez écrire un beau code déclaratif sans lire des centaines de pages du manuel et sans même étudier les profondeurs théoriques de la programmation fonctionnelle - il est peu probable que vous ayez à utiliser, disons, des monades, car mrr lui-même est une monade géante qui sépare le calcul pur des mutations d'état.Alors que dans d'autres bibliothèques, des approches hétérogènes (impératives à l'aide de méthodes et déclaratives à l'aide de liants réactifs) coexistent souvent, dont le programmeur mélange au hasard «salade», dans mrr il y a une seule essence de base - un flux, qui contribue à l'homogénéité et à l'uniformité du code. Confort, commodité, simplicité, gain de temps pour un programmeur sont les principaux avantages de mrr (à partir d'ici, un autre décodage de mrr en «mrrrr», c'est-à-dire ronronner un chat satisfait de la vie d'un chat).Quels sont les inconvénients?
La programmation avec des "lignes" a ses avantages et ses inconvénients. Vous ne pourrez pas saisir automatiquement le nom de la cellule ni rechercher l'endroit où elle est définie. D'autre part, dans mrr, il y a toujours un et un seul endroit où le comportement de la cellule est déterminé, et il est facile de le trouver avec une simple recherche de texte, tout en recherchant l'endroit où la valeur du champ Redux du magasin est déterminée, ou encore moins le champ d'état lors de l'utilisation de l'ensemble natif. peut être plus long.Qui pourrait être intéressé par cela?
Tout d'abord, les adeptes de la programmation fonctionnelle sont des personnes pour qui l'avantage d'une approche déclarative est évident. Bien sûr, les solutions casher ClojureScript existent déjà, mais elles restent un produit de niche, tandis que React gouverne la balle. Si votre projet utilise déjà Redux, vous pouvez commencer à utiliser mrr pour gérer l'état local et éventuellement basculer sur global. Même si vous ne comptez pas utiliser de nouvelles technologies pour le moment, vous pouvez traiter avec mrr afin de "vous étirer le cerveau" en regardant les tâches familières sous un nouveau jour, car mrr est significativement différent des bibliothèques de gestion d'état courantes.Cela peut-il déjà être utilisé?
En principe, oui :) La bibliothèque est jeune, jusqu'à présent elle a été activement utilisée sur plusieurs projets, mais l'API de la fonctionnalité de base a déjà été établie, maintenant le travail se fait principalement sur différentes lotions (sucre syntaxique), conçues pour accélérer et faciliter le développement. Soit dit en passant, dans les principes de mrr eux-mêmes, il n'y a rien de spécifique à React, il peut être adapté pour une utilisation avec n'importe quelle bibliothèque de composants (React a été choisi en raison du manque de réactivité intégrée ou d'une bibliothèque généralement acceptée pour cela).Merci de votre attention, je serai reconnaissant pour les commentaires et critiques constructives!