Reagieren + Mobx: Was ist der Sinn?

Heute möchte ich Ihnen erzählen, wie der Übergang zu Mobx in unserem Projekt stattgefunden hat und welche Vorteile es bietet. Ein typisches Projekt wird ebenfalls gezeigt und es werden Erklärungen zu den Hauptthemen gegeben. Aber zuerst eine Einführung.

Bild

Warum müssen Sie zu etwas wechseln? In der Tat ist die Antwort auf diese Frage bereits die halbe Miete. Viele lieben es, neue Technologien nur deshalb anzuwenden, weil sie neu sind. Eine gute Linie im Lebenslauf, die Möglichkeit der Selbstentwicklung, im Trend zu sein. Es ist großartig, wenn Sie einfach vorwärts gehen können.

Und doch muss jedes Tool seine eigenen Probleme lösen, und wir stoßen sie auf die eine oder andere Weise ab, wenn wir kommerziellen Code schreiben.

In unserem Projekt gibt es eine Reihe von Widgets, in denen der Benutzer seine Daten eingibt und mit Formularen interagiert. In der Regel verfügt jedes Widget über mehrere Bildschirme. Es war einmal, dass alles mit der guten alten MarkoJS + Templating Engine funktionierte, für die jQuery auf dem Client erforderlich war. Die Interaktion mit Formularen wurde in einem imperativen Stil geschrieben, wenn ... sonst Rückrufe und dies alles das gleiche ist, was bereits in der Vergangenheit zu sein scheint.

Dann kam die Zeit für React . Die Geschäftslogik auf dem Client wurde immer umfangreicher, es gab viele Interaktionsoptionen, der zwingende Code wurde zu einem komplizierten Durcheinander. Der deklarative Reaktionscode erwies sich als viel praktischer. Endlich war es möglich, sich eher auf Logik als auf Präsentation zu konzentrieren, Komponenten wiederzuverwenden und Aufgaben zur Entwicklung neuer Funktionen einfach auf verschiedene Mitarbeiter zu verteilen.

Die Anwendung auf pure React im Laufe der Zeit beruht jedoch auf greifbaren Grenzen. Natürlich ist es uns langweilig, this.setState für alle zu schreiben und über seine Asynchronität nachzudenken, aber das Werfen von Daten und Rückrufen durch die Dicke der Komponenten macht es besonders schwierig. Kurz gesagt, es ist an der Zeit, Daten und Präsentation vollständig zu trennen. Es geht nicht darum, wie Sie es mit reinem React schaffen können, aber in den Branchen-Frameworks, die die Flux-Architektur von Front-End-Anwendungen implementieren, war dies in letzter Zeit sehr beliebt.

Nach der Anzahl der Artikel und Referenzen in offenen Stellen ist Redux der bekannteste unter uns. Eigentlich habe ich bereits meine Hand mitgebracht, um es in unserem Projekt zu installieren und mit der Entwicklung zu beginnen, wie im allerletzten Moment (und das ist buchstäblich!). Der Teufel zog durch den Habr, und dann gab es nur eine Diskussion über das Thema "Redux oder Mobx?" Hier ist dieser Artikel: habr.com/en/post/459706 . Nachdem ich es gelesen hatte, sowie alle Kommentare darunter, wurde mir klar, dass ich Mobx immer noch verwenden würde .

Also nochmal. Die Antwort auf die wichtigste Frage - warum das alles? - Es sieht so aus: Es ist Zeit, die Präsentation und die Daten zu trennen. Ich möchte die Datenverwaltung in einem deklarativen Stil (wie Zeichnung) erstellen, ohne gegenseitige Befruchtung von Rückrufen und weitergeleiteten Attributen.

Jetzt können wir fortfahren.

1. Über die Anwendung


Wir müssen an der Front einen Designer von Bildschirmen und Formularen bauen, die dann schnell gemischt und entsprechend den sich ändernden Anforderungen des Geschäfts miteinander verbunden werden können. Dies führt uns unweigerlich zu Folgendem: Erstellen einer Sammlung vollständig isolierter Komponenten sowie einiger grundlegender Komponenten, die jedem unserer Widgets entsprechen (tatsächlich handelt es sich um separate SPAs, die jedes Mal unter einem neuen Geschäftsfall in der allgemeinen Anwendung erstellt werden).

Die Beispiele zeigen eine abgeschnittene Version eines dieser Gadgets. Um keinen zusätzlichen Code anzuhäufen, lassen Sie ihn aus drei Eingabefeldern und Schaltflächen bestehen.

2. Daten


Mobx ist im Wesentlichen kein Framework, sondern nur eine Bibliothek. Das Handbuch besagt ausdrücklich, dass Ihre Daten nicht direkt organisiert werden. Sie selbst müssen sich eine solche Organisation einfallen lassen. Übrigens verwenden wir Mobx 4, da Version 5 den Datentyp Sybmol verwendet, der leider nicht von allen Browsern unterstützt wird.

Daher werden alle Daten separaten Entitäten zugeordnet. Unsere Anwendung zielt auf zwei Ordner ab:
- Komponenten, bei denen wir alle Ansichten setzen
- speichert , die Daten enthalten, sowie die Logik der Arbeit mit ihnen.
Eine für uns typische Dateneingabekomponente besteht beispielsweise aus zwei Dateien: Input.js und InputStore.js . Die erste Datei ist eine blöde React-Komponente, die ausschließlich für die Anzeige verantwortlich ist, die zweite sind die Daten dieser Komponente, Benutzerregeln ( onClick , onChange usw.)

Bevor wir direkt zu den Beispielen gehen, müssen wir ein weiteres wichtiges Problem lösen.

3. Management


Nun, wir haben völlig autonome Komponenten des View-Store, aber wie kommen wir in einer ganzen Anwendung zusammen? Zur Anzeige haben wir die Stammkomponente von App.js , und zur Verwaltung der Datenflüsse ist der Hauptspeicher mainStore.js . Das Prinzip ist einfach: mainStore weiß alles über alle Repositorys aller erforderlichen Komponenten (es wird unten gezeigt, wie dies erreicht wird). Andere Repositories wissen überhaupt nichts über die Welt (nun, es wird eine Ausnahme geben - Wörterbücher). So wissen wir garantiert, wohin unsere Daten gehen und wo wir sie abfangen können.

Bild

mainStore kann deklarativ durch Ändern von Teilen seines Status den Rest der Komponenten steuern. In der folgenden Abbildung beziehen sich Aktionen und Status auf Komponentenspeicher und berechnete Werte auf mainStore :

Bild

Beginnen wir mit dem Schreiben des Codes. Die Hauptanwendungsdatei index.js:

import React from "react"; import ReactDOM from "react-dom"; import {Provider} from "mobx-react"; import App from "./components/App"; import mainStore from "./stores/mainStore"; import optionsStore from "./stores/optionsStore"; //  IE11 require("es6-object-assign").polyfill(); require( "./static/less/main.less"); const stores = { mainStore, optionsStore, ButtonStore : mainStore.ButtonStore, FioStore : mainStore.FioStore, EmailStore : mainStore.EmailStore }; ReactDOM.render(( <Provider {...stores}> <App /> </Provider> ), document.getElementById('reactContainer')); 

Hier sehen Sie das Grundkonzept von Mobx. Daten (Speicher) sind über den Provider- Mechanismus überall in der Anwendung verfügbar. Wir verpacken unsere Bewerbung, indem wir die benötigten Lagermöglichkeiten auflisten. Um den Provider zu nutzen , schließen wir das mobx-react-Modul an . Damit der Hauptsteuerungsspeicher mainStore von Anfang an auf alle anderen Daten zugreifen kann, initialisieren wir die untergeordneten Speicher in mainStore :

 // mainStore.js import optionsStore from "./optionsStore"; import ButtonStore from "./ButtonStore"; import FioStore from "./FioStore"; import EmailStore from "./EmailStore"; .... class mainStore { constructor() { /** *    */ this.ButtonStore = new ButtonStore(); this.FioStore = new FioStore(); this.EmailStore = new EmailStore(); ... 


Jetzt App.js , das Grundgerüst unserer Anwendung

 import React from "react"; import {observer, inject} from "mobx-react"; import ButtonArea from "./ButtonArea"; import Email from "./Email"; import Fio from "./Fio"; import l10n from "../../../l10n/localization.js"; @inject("mainStore") @observer export default class App extends React.Component { constructor(props) { super(props); }; render() { const mainStore = this.props.mainStore; return ( <div className="container"> <Fio label={l10n.ru.profile.name} name={"name"} value={mainStore.userData.name} daData={true} /> <Fio label={l10n.ru.profile.surname} name={"surname"} value={mainStore.userData.surname} daData={true} /> <Email label={l10n.ru.profile.email} name={"email"} value={mainStore.userData.email} /> <ButtonArea /> </div> ); } } 

Es gibt zwei weitere grundlegende Mobx-Konzepte - Injizieren und Beobachten .
Injizieren implementiert nur den erforderlichen Speicher in der Anwendung. Verschiedene Teile unserer Anwendung verwenden unterschiedliche Repositorys, die wir in inj auflisten , getrennt durch Kommas. Natürlich sollten steckbare Geschäfte zunächst im Anbieter aufgeführt sein . Repositorys sind in der Komponente über this.props.yourStoreName verfügbar .
Beobachter - Der Dekorateur gibt an, dass unsere Komponente Daten abonniert, die mit Mobx geändert wurden. Die Daten haben sich geändert - in der Komponente ist eine Reaktion aufgetreten (dies wird unten gezeigt). Somit keine speziellen Abonnements und Rückrufe - Mobx liefert die Änderungen selbst!

Wir werden wieder die gesamte Anwendung im mainStore verwalten , aber vorerst werden wir die Komponenten übernehmen. Wir haben drei Arten von ihnen - Fio , E-Mail , Button . Lassen Sie das erste und dritte universell und E-Mail- benutzerdefiniert sein. Beginnen wir mit ihm.

Das Display ist die übliche dumme React-Komponente:

Email.js
 import React from "react"; import {inject, observer} from 'mobx-react'; @inject("EmailStore") @observer export default class Email extends React.Component { constructor(props) { super(props); }; componentDidMount = () => { this.props.EmailStore.validate(this.props.name); }; componentWillUnmount = () => { this.props.EmailStore.unmount(this.props.name); }; render() { const name = this.props.name; const EmailStore = this.props.EmailStore; const params = EmailStore.params; let status = "form-group email "; if (params.isCorrect && params.onceValidated) status += "valid"; if (params.isWrong && params.onceValidated) status += "error"; return ( <div className={status}> <label htmlFor={name}>{this.props.label}</label> <input type="email" disabled={this.props.disabled} name={name} id={name} value={params.value} onChange={(e) => EmailStore.bindData(e, name)} /> </div> ); } } 

Wir verbinden die externe Validierungskomponente. Dies ist wichtig, nachdem das Element bereits im Layout enthalten ist. Daher wird die Methode aus dem Speicher in componentDidMount aufgerufen.

Nun das Repository selbst:

EmailStore.js
 import {action, observable} from 'mobx'; import reactTriggerChange from "react-trigger-change"; import Validators from "../../../helpers/Validators"; import { getTarget } from "../../../helpers/elementaries"; export default class EmailStore { @observable params = { value : "", disabled : null, isCorrect : null, isWrong : null, onceValidated : null, prevalidated : null } /** *    */ @action bindData = (e, name) => { this.params.value = getTarget(e).value; }; /** *   */ @action validate = (name) => { const callbacks = { success : (formatedValue) => { this.params.value = formatedValue; this.params.isCorrect = true; this.params.isWrong = false; this.params.onceValidated = true; }, fail : (formatedValue) => { this.params.value = formatedValue; this.params.isCorrect = false; this.params.isWrong = true; } }; const options = { type : "email" }; const element = document.getElementById(name); new Validators(element, options, callbacks).init(); //    reactTriggerChange(element); this.params.prevalidated = true; }; } 


Es lohnt sich, auf zwei neue Einheiten zu achten.

beobachtbar - ein Objekt, dessen Änderung von Feldern von Mobx überwacht wird (und Signale an den Beobachter sendet, der unseren spezifischen Speicher abonniert hat).
Aktion - Dieser Dekorateur sollte jeden Handler umschließen, der den Status der Anwendung ändert und / oder Nebenwirkungen verursacht. Hier ändern wir den Wert von value in @observable params .

Das war's, unsere einfache Komponente ist fertig! Er kann Benutzerdaten verfolgen und aufzeichnen. Später werden wir sehen, wie das zentrale Repository von mainStore diese Daten abonniert.

Jetzt eine typische Fio- Komponente. Der Unterschied zum vorherigen besteht darin, dass wir Komponenten dieses Typs unbegrenzt oft in einer Anwendung verwenden werden. Dies stellt einige zusätzliche Anforderungen an den Komponentenspeicher. Darüber hinaus werden wir mithilfe des hervorragenden DaData- Dienstes weitere Hinweise zu den Eingabezeichen geben . Anzeige:

Fio.js
 import React from "react"; import {inject, observer} from 'mobx-react'; import {get} from 'mobx'; @inject("FioStore") @observer export default class Fio extends React.Component { constructor(props) { super(props); }; componentDidMount = () => { /** *       */ this.props.FioStore.registration(this.props); }; componentWillUnmount = () => { this.props.FioStore.unmount(this.props.name); }; render() { /** *       *  DaData: * data.surname -  * data.name -  * https://dadata.ru/api/suggest/name */ const FioStore = this.props.FioStore; const name = this.props.name; const item = get(FioStore.items, name); if (item && item.isCorrect && item.onceValidated && !item.prevalidated) status = "valid"; if (item && item.isWrong && item.onceValidated) status = "error"; //    store let value = this.props.value; if (item) value = item.value; return ( <div className="form-group fio"> <label htlmfor={name}>{this.props.label}</label> <input type="text" disabled={this.props.disabled} name={name} id={name} value={value} onChange={(e) => FioStore.bindData(e, name)} /> {(item && item.suggestions && item.suggestions.length > 0) && <div className="hint-container" id={"hint-container-" + item.id}>{item.suggestions.map((suggestion, i) => { return ( <div className={"suggestion-item fs-" + i} key={i} value={suggestion.data[name]} onClick={(e) => FioStore.setSuggestion(e, name)}> <span className="suggestion-text">{suggestion.data[name]}</span> </div>) })}</div>} </div> ); } } 

Hier gibt es etwas Neues: Wir greifen nicht direkt auf den Status der Komponente zu, sondern über get :
 get(FioStore.items, name) 

Tatsache ist, dass die Anzahl der Komponenteninstanzen unbegrenzt ist und das Repository eine für alle Komponenten dieses Typs ist. Daher geben wir bei der Registrierung die Parameter jeder Instanz in Map ein :

Fiostore.js
 import {action, autorun, observable, get, set} from 'mobx'; import reactTriggerChange from "react-trigger-change"; import Validators from "../../../helpers/Validators"; import { getDaData, blockValidate } from "../../../helpers/functions"; import { getAttrValue, scrollToElement, getTarget } from "../../../helpers/elementaries"; export default class FioStore { constructor() { autorun(() => { /** *        .         setSuggestion() */ const self = this; $("body").click((e) => { if (e.target.className !== "suggestion-item" && e.target.className !== "suggestion-text") { const items = self.items.entries(); for (var [key, value] of items) { value.suggestions = []; } } }); }) } /** *   items       Fio    */ @observable items = new Map([]); /** *    */ @action registration = (params) => { const nameExists = get(this.items, params.name); if (!blockValidate({params, nameExists, type: "Fio"})) return false; //  items   const value = { value : params.value, disabled : params.disabled, isCorrect : null, isWrong : null, suggestions : [], daData : params.daData, startValidation : true, //      onceValidated : false, //      prevalidated : false }; set(this.items, params.name, value); this.validate(params.name); }; /** *    */ @action unmount = (name) => { this.items.delete(name); }; /** *    *    */ @action bindData = (e, name) => { const value = getTarget(e).value; const item = get(this.items, name); /** *     DaData */ if (item.daData && !item.startValidation) { getDaData({value, type: "fio", name}) .then((result) => { item.suggestions = result.suggestions; }) .catch((error) => {console.log(error)}) } else { item.startValidation = false; item.value = value; } }; /** *   */ @action setSuggestion = (e, name) => { if (e) e.preventDefault(); get(this.items, name).value = getAttrValue(e); //     get(this.items, name).suggestions = []; get(this.items, name).isCorrect = true; get(this.items, name).isWrong = false; }; /** *   */ @action validate = (name) => { const callbacks = { success : (formatedValue) => { get(this.items, name).value = formatedValue; get(this.items, name).isCorrect = true; get(this.items, name).isWrong = false; get(this.items, name).onceValidated = true; }, fail : (formatedValue) => { get(this.items, name).value = formatedValue; get(this.items, name).isCorrect = false; get(this.items, name).isWrong = true; } }; const options = { type : "fio" }; const element = document.getElementById(name); new Validators(element, options, callbacks).init(); //    reactTriggerChange(element); get(this.items, name).prevalidated = true; }; } 

Der Status unserer generischen Komponente wird wie folgt initialisiert:

 @observable items = new Map([]); 

Es wäre bequemer, mit einem regulären JS-Objekt zu arbeiten, es wird jedoch nicht "getippt", wenn die Werte seiner Felder geändert werden, weil Felder werden dynamisch hinzugefügt, wenn der Seite neue Komponenten hinzugefügt werden. Wenn wir DaData-Hinweise erhalten, nehmen wir diese separat heraus.

Die Schaltflächenkomponente sieht ähnlich aus, es gibt jedoch keine Tipps:

Button.js
 import React from "react"; import {inject, observer} from 'mobx-react'; @inject("ButtonStore") @observer export default class CustomButton extends React.Component { constructor(props) { super(props); }; componentDidMount = () => { /** *       */ this.props.ButtonStore.registration(this.props); }; componentWillUnmount = () => { this.props.ButtonStore.unmount(this.props.name); }; render() { const name = this.props.name; return ( <div className="form-group button"> <button disabled={this.props.disabled} onClick={(e) => this.props.ButtonStore.bindClick(e, name)} name={name} id={name} >{this.props.text}</button> </div> ); } } 


ButtonStore.js
 import {action, observable, get, set} from 'mobx'; import {blockValidate} from "../../../helpers/functions"; export default class ButtonStore { constructor() {} /** *   items       Button    */ @observable items = new Map([]) /** *    */ @action registration = (params) => { const nameExists = get(this.items, params.name); if (!blockValidate({params, nameExists, type: "Button"})) return false; //  items   const value = { disabled : params.disabled, isClicked : false }; set(this.items, params.name, value); }; /** *    */ @action unmount = (name) => { this.items.delete(name); }; /** *    */ @action bindClick = (e, name) => { e.preventDefault(); get(this.items, name).isClicked = true; }; } 


Die Komponente des Buttons wird von der HOC-Komponente von ButtonArea umschlossen . Bitte beachten Sie, dass die ältere Komponente eigene und die jüngste Filiale enthält. In Ketten verschachtelter Komponenten müssen keine Parameter und Rückrufe weitergeleitet werden. Alles, was für den Betrieb einer bestimmten Komponente benötigt wird, wird direkt hinzugefügt.

ButtonArea.js
 import React from "react"; import {inject, observer} from 'mobx-react'; import l10n from "../../../l10n/localization.js"; import Button from "./Button"; @inject("mainStore", "optionsStore") @observer export default class ButtonArea extends React.Component { constructor(props) { super(props); }; render() { return ( <div className="button-container"> <p>{this.props.optionsStore.dict.buttonsHeading}</p> <Button name={"send_data"} disabled={this.props.mainStore.buttons.sendData.disabled ? true : false} text={l10n.ru.common.continue} /> </div> ); } } 


Wir haben also alle Komponenten bereit. Die Angelegenheit bleibt beim Manager von mainStore. Zunächst der gesamte Speichercode:

mainStore.js
 import {observable, computed, autorun, reaction, get, action} from 'mobx'; import optionsStore from "./optionsStore"; import ButtonStore from "./ButtonStore"; import FioStore from "./FioStore"; import EmailStore from "./EmailStore"; import { fetchOrdinary, sendStats } from "../../../helpers/functions"; import l10n from "../../../l10n/localization.js"; class mainStore { constructor() { /** *    */ this.ButtonStore = new ButtonStore(); this.FioStore = new FioStore(); this.EmailStore = new EmailStore(); autorun(() => { this.fillBlocks(); this.fillData(); }); /** *      */ reaction( () => this.dataInput, (result) => { let isIncorrect = false; for (let i in result) { for (let j in result[i]) { const res = result[i][j]; if (!res.isCorrect) isIncorrect = true; this.userData[j] = res.value; } }; if (!isIncorrect) { this.buttons.sendData.disabled = false } else { this.buttons.sendData.disabled = true }; } ); /** *     */ reaction( () => this.sendDataButton, (result) => { if (result) { if (result.isClicked) { get(this.ButtonStore.items, "send_data").isClicked = false; const authRequestSuccess = () => { console.log("request is success!") }; const authRequestFail = () => { console.log("request is fail!") }; const request = { method : "send_userdata", params : { name : this.userData.name, surname : this.userData.surname, email : this.userData.email } }; console.log("Request body is:"); console.log(request); fetchOrdinary( optionsStore.OPTIONS.sendIdentUrl, JSON.stringify(request), { success: authRequestSuccess, fail: authRequestFail } ); } } } ); } @observable userData = { name : "", surname : "", email : "" }; @observable buttons = { sendData : { disabled : true } }; /** *     * @key -   * @value -      (name, type),          */ componentsMap = { userData : [ ["name", "fio"], ["surname", "fio"], ["email", "email"], ["send_data", "button"] ] }; /** *     listener'  stores */ @observable listenerBlocks = {}; /** *    */ @action fillBlocks = () => { for (let i in this.componentsMap) { const pageBlock = this.componentsMap[i]; //      (key)     (value) const blocks = {}; pageBlock.forEach((item, i) => { const _name = item[0]; const _type = item[1]; if (!blocks[_type]) { blocks[_type] = [_name] } else { blocks[_type].push(_name) } }) this.listenerBlocks[i] = blocks; } }; /** *    */ @action fillData = () => { if (optionsStore.preset) { // ,   undefined,     if (optionsStore.preset.name) this.userData.name = optionsStore.preset.name; if (optionsStore.preset.surname) this.userData.surname = optionsStore.preset.surname; } }; /** * Listener    */ @computed get dataInput() { const mappedResult = { fio : {}, email : { email : {} } }; if (this.FioStore && this.FioStore.items) { this.listenerBlocks.userData.fio.forEach((item) => { const i = get(this.FioStore.items, item); if (i) { mappedResult.fio[item] = { isCorrect : i.isCorrect, value : i.value } } }) } if (this.EmailStore && this.EmailStore.params) { mappedResult.email.email = { isCorrect : this.EmailStore.params.isCorrect, prevalidated : this.EmailStore.params.prevalidated, value : this.EmailStore.params.value } } return mappedResult } /** * Listener     */ @computed get sendDataButton() { let result = {}; const i = get(this.ButtonStore.items, "send_data"); if (i) { result = { isClicked : i.isClicked } } return result } } export default new mainStore(); 


Noch ein paar Schlüsselelemente.

computed ist ein Dekorator für Funktionen, die Änderungen in unserem Observable verfolgen. Ein wichtiger Vorteil von Mobx ist, dass nur Daten verfolgt werden, die berechnet und als Ergebnis zurückgegeben werden. Die Reaktion und infolgedessen das Neuzeichnen des viralen DOM erfolgt nur bei Bedarf.
Reaktion - ein Werkzeug zum Organisieren von Nebenwirkungen basierend auf einem veränderten Zustand. Es werden zwei Funktionen benötigt: die erste berechnet, wobei der berechnete Zustand zurückgegeben wird, die zweite mit Effekten, die den Zustandsänderungen folgen sollen. In unserem Beispiel wird die Reaktion zweimal angewendet. Im ersten Teil betrachten wir den Status der Felder und schließen daraus, ob das gesamte Formular korrekt ist, und zeichnen auch den Wert jedes Felds auf. Im zweiten Schritt klicken wir auf eine Schaltfläche (genauer gesagt, wenn ein Zeichen „Schaltfläche gedrückt“ ist) und senden Daten an den Server. Das Datenobjekt wird in der Browserkonsole angezeigt. Da mainStore alle Repositorys kennt, können wir es uns unmittelbar nach der Verarbeitung des Klickens einer Schaltfläche leisten, das Flag in einem zwingenden Stil zu deaktivieren:

 get(this.ButtonStore.items, "send_data").isClicked = false; 

Sie können diskutieren, wie akzeptabel das Vorhandensein eines solchen "Imperativs" ist, aber in jedem Fall erfolgt die Steuerung nur in eine Richtung - von mainStore zu ButtonStore .
Autorun wird verwendet, wenn einige Aktionen direkt ausgeführt werden sollen, nicht als Reaktion auf das Speichern von Änderungen. In unserem Beispiel wird eine Hilfsfunktion gestartet und die Formularfelder mit Daten aus dem Wörterbuch vorab gefüllt.

Die Reihenfolge der Aktionen, die wir haben, ist also wie folgt. Komponenten verfolgen Benutzerereignisse und ändern ihren Status. mainStore through berechnet berechnet das Ergebnis nur basierend auf dem Status, der sich geändert hat. Verschiedene berechnete suchen nach Änderungen in verschiedenen Zuständen in verschiedenen Repositorys. Darüber hinaus führen wir durch Reaktion auf der Grundlage berechneter Ergebnisse Aktionen mit Observablen sowie Nebenwirkungen durch (z. B. stellen wir eine AJAX-Anfrage). Obsevables abonniert untergeordnete Komponenten, die bei Bedarf neu gezeichnet werden. Ein unidirektionaler Datenstrom mit voller Kontrolle darüber, wo und was sich ändert.

Sie können das Beispiel und den Code selbst ausprobieren . Link zum Repository: github.com/botyaslonim/mobx-habr .
Dann wie gewohnt: npm i , npm lokal laufen . Im öffentlichen Ordner die Datei index.html . DaData-Hinweise funktionieren auf meinem kostenlosen Konto, daher können sie aufgrund des Habr-Effekts wahrscheinlich an einigen Stellen fallen.

Ich freue mich über konstruktive Kommentare und Vorschläge zur Arbeit von Anwendungen auf Mobx!

Abschließend möchte ich sagen, dass die Bibliothek die Arbeit mit Daten erheblich vereinfacht hat. Für kleine und mittlere Anwendungen ist es auf jeden Fall ein sehr praktisches Werkzeug, die Eigenschaften von Komponenten und Rückrufen zu vergessen und sich direkt auf die Geschäftslogik zu konzentrieren.

Source: https://habr.com/ru/post/de471048/


All Articles