Praktische Richtlinien für die Entwicklung umfangreicher React-Anwendungen. Teil 2: Zustandsverwaltung, Routing

Heute veröffentlichen wir den zweiten Teil der Übersetzung des Materials, der der Entwicklung groß angelegter React-Anwendungen gewidmet ist. Hier werden wir über das Verwalten des Status von Anwendungen, das Routing und die Schnittstellenentwicklung sprechen.



Teil 1: Praktische Richtlinien für die Entwicklung umfangreicher React-Anwendungen. Planung, Aktionen, Datenquellen und APIs

Teil 2: Praktische Richtlinien für die Entwicklung umfangreicher React-Anwendungen. Teil 2: Zustandsverwaltung, Routing


Anwendungsstatusverwaltung, Redux-Integration, Routing-Organisation


Hier werden wir darüber sprechen, wie Sie die Funktionalität von Redux erweitern können, um komplexe Vorgänge in der Anwendung ordnungsgemäß ausführen zu können. Wenn solche Mechanismen schlecht implementiert sind, können sie das beim Entwerfen des Repositorys verwendete Muster verletzen.

JavaScript- Generatorfunktionen können viele der Probleme lösen, die mit der asynchronen Programmierung verbunden sind. Tatsache ist, dass diese Funktionen auf Wunsch des Programmierers gestartet und gestoppt werden können. Die Redux-Saga-Middleware verwendet dieses Konzept, um problematische Aspekte einer Anwendung zu verwalten. Insbesondere geht es um die Lösung solcher Probleme, die nicht mit Hilfe von Reduzierern gelöst werden können, die in Form von reinen Funktionen dargestellt werden.

▍Lösen von Aufgaben, die mit reinen Funktionen nicht gelöst werden können


Stellen Sie sich das folgende Szenario vor. Ihnen wurde angeboten, an einer Anwendung zu arbeiten, die für ein Unternehmen entwickelt wurde, das auf dem Immobilienmarkt tätig ist. Der Kunde möchte eine neue, bessere Website erhalten. Zu Ihrer Verfügung steht eine REST-API, Sie haben Layouts aller Seiten mit Hilfe von Zapier erstellt, Sie haben einen Plan der Anwendung skizziert. Aber dann kam ein schweres Problem auf.

Das Kundenunternehmen verwendet seit langem ein bestimmtes Content Management System (CMS). Die Mitarbeiter des Unternehmens kennen dieses System sehr gut, sodass der Kunde nicht auf das neue CMS umsteigen möchte, um das Schreiben neuer Beiträge im Unternehmensblog zu vereinfachen. Darüber hinaus müssen Sie vorhandene Veröffentlichungen aus dem Blog auf eine neue Site kopieren. Dies kann auch zu einem Problem führen.

Das Gute ist, dass das vom Client verwendete CMS über eine praktische API verfügt, über die Sie über das Blog auf Veröffentlichungen zugreifen können. Wenn Sie jedoch einen Agenten für die Arbeit mit dieser API erstellt haben, wird die Situation durch die Tatsache kompliziert, dass er sich auf einem bestimmten Server befindet, auf dem die Daten überhaupt nicht nach Bedarf dargestellt werden.

Dies ist ein Beispiel für ein Problem, das den Anwendungscode verschmutzen kann, da Sie hier Mechanismen in das Projekt einbeziehen müssen, um mit der neuen API zu arbeiten, die zum Herunterladen von Blog-Posts verwendet wird. Sie können mit dieser Situation mit Redux-Saga umgehen.
Schauen Sie sich das folgende Diagramm an. So interagieren unsere Anwendung und API. Wir laden Publikationen im Hintergrund mit Redux-Saga herunter.


Anwendungsdiagramm Verwenden von Redux- und Redux-Saga-Speicher

Hier GET.BLOGS die Komponente die Aktion GET.BLOGS . Die Anwendung verwendet Redux-Saga, daher wird diese Anfrage abgefangen. Danach lädt die Generatorfunktion im Hintergrund Daten aus dem Datenspeicher herunter und aktualisiert den von Redux unterstützten Anwendungsstatus.

Hier ist ein Beispiel dafür, wie die Generatorfunktion zum Laden von Veröffentlichungen (solche Funktionen werden als "Sagen" bezeichnet) in der beschriebenen Situation aussehen könnte. Sagas können in anderen Szenarien verwendet werden. Zum Beispiel, um die Speicherung von Benutzerdaten zu organisieren (z. B. können es Token sein), da dies ein weiteres Beispiel für eine Aufgabe ist, für die reine Funktionen nicht geeignet sind.

 ... function* fetchPosts(action) { if (action.type === WP_POSTS.LIST.REQUESTED) {   try {     const response = yield call(wpGet, {       model: WP_POSTS.MODEL,       contentType: APPLICATION_JSON,       query: action.payload.query,     });     if (response.error) {       yield put({         type: WP_POSTS.LIST.FAILED,         payload: response.error.response.data.msg,       });       return;         yield put({       type: WP_POSTS.LIST.SUCCESS,       payload: {         posts: response.data,         total: response.headers['x-wp-total'],         query: action.payload.query,       },       view: action.view,     });   } catch (e) {     yield put({ type: WP_POSTS.LIST.FAILED, payload: e.message });  ... 

Die hier vorgestellte Saga erwartet Aktionen wie WP_POSTS.LIST.REQUESTED . Durch den Empfang einer solchen Aktion werden Daten von der API geladen. Nach dem Empfang der Daten sendet sie eine weitere Aktion - WP_POSTS.LIST.SUCCESS . Die Verarbeitung führt zur Aktualisierung des Repositorys mit dem entsprechenden Reduzierer.

▍ Einführung von Reduzierstücken


Bei der Entwicklung großer Anwendungen ist es unmöglich, das Gerät aller erforderlichen Modelle im Voraus zu planen. Darüber hinaus hilft die Verwendung der Technologie zur Einführung von Reduzierern mit zunehmender Größe der Anwendung, eine große Menge an Arbeitsstunden einzusparen. Mit dieser Technik können Entwickler dem System neue Reduzierungen hinzufügen, ohne das gesamte Repository neu schreiben zu müssen.

Es gibt Bibliotheken , mit denen dynamische Redux-Repositorys erstellt werden können. Ich bevorzuge jedoch den Mechanismus der Einführung von Reduzierern, da er dem Entwickler ein gewisses Maß an Flexibilität verleiht. Beispielsweise kann eine vorhandene Anwendung mit diesem Mechanismus ausgestattet werden, ohne dass die Anwendung ernsthaft neu organisiert werden muss.

Die Einführung von Reduzierern ist eine Form der Codetrennung. Die Entwicklergemeinde von React begrüßt diese Technologie mit Begeisterung. Ich werde diesen Code verwenden, um das Erscheinungsbild und die Merkmale des Implementierungsmechanismus der Reduzierungen zu demonstrieren.

Schauen wir uns zunächst die Integration mit Redux an:

 ... const withConnect = connect( mapStateToProps, mapDispatchToProps, ); const withReducer = injectReducer({ key: BLOG_VIEW, reducer: blogReducer, }); class BlogPage extends React.Component {  ... } export default compose( withReducer, withConnect, )(BlogPage); 

Dieser Code ist Teil der Datei BlogPage.js , die die Anwendungskomponente enthält.

Hier verwenden wir im Exportbefehl nicht die connect Funktion, sondern die compose Funktion. Dies ist eine der Funktionen der Redux-Bibliothek, mit der Sie mehrere Funktionen zusammenstellen können. Die Liste der zum compose Funktionen muss von rechts nach links oder von unten nach oben gelesen werden.

Aus der Redux- Dokumentation können Sie lernen, dass Sie mit der compose Funktion Transformationen tief verschachtelter Funktionen erstellen können. In diesem Fall ist der Programmierer von der Notwendigkeit befreit, sehr lange Strukturen zu verwenden. Diese Konstruktionen sehen aus wie Codezeilen, die Aufrufe an einige Funktionen darstellen, wobei die Ergebnisse von Aufrufen an andere Funktionen als Argumente übergeben werden. In der Dokumentation wird darauf hingewiesen, dass die compose Funktion mit Vorsicht verwendet werden sollte.

Die Funktion ganz rechts in einer Komposition kann viele Argumente annehmen, aber nur ein Argument kann an Funktionen übergeben werden, die darauf folgen. Infolgedessen übergeben wir durch Aufrufen der Funktion, die sich aus der Verwendung von compose ergibt, das, was von den ursprünglichen Funktionen stammt, rechts von allen anderen. Aus diesem Grund haben wir die withConnect Funktion als letzten Parameter an die withConnect Funktion übergeben. Infolgedessen kann die compose Funktion genauso wie die connect Funktion verwendet werden.

▍ Routing und Redux


Es gibt eine Reihe von Tools, mit denen Routingprobleme in Anwendungen gelöst werden können. In diesem Abschnitt konzentrieren wir uns jedoch auf die React-Router-Dom- Bibliothek. Wir werden seine Funktionen erweitern, damit es mit Redux zusammenarbeiten kann.

Am häufigsten wird der React-Router folgendermaßen verwendet: Die BrowserRouter ist im BrowserRouter Tag enthalten, und die BrowserRouter Container werden in die withRouter() -Methode eingeschlossen und exportiert ( hier ein Beispiel).

Bei diesem Ansatz empfängt die untergeordnete Komponente über den props ein history , das einige Eigenschaften enthält, die für die aktuelle Benutzersitzung spezifisch sind. In diesem Objekt gibt es einige Methoden, mit denen die Navigation gesteuert werden kann.

Diese Routing-Option kann in großen Anwendungen zu Problemen führen. Dies liegt an der Tatsache, dass sie kein zentrales history . Außerdem können Komponenten, die nicht mit <Route> gerendert werden, nicht mit dem history . Hier ist ein Beispiel mit <Route> :

 <Route path="/" exact component={HomePage} /> 

Um dieses Problem zu lösen, verwenden wir die Connected-React-Router- Bibliothek, mit der wir das Routing mithilfe der dispatch einrichten können. Die Integration dieser Bibliothek in das Projekt erfordert einige Änderungen. Insbesondere muss ein neuer Reduzierer speziell für Routen entwickelt werden (dies ist ziemlich offensichtlich) und dem System einige Hilfsmechanismen hinzugefügt werden.

Nach Abschluss der Konfiguration kann das neue Routing-System über Redux verwendet werden. Die Navigation in der Anwendung kann also durch Senden von Aktionen implementiert werden.

Um die Funktionen der Connected-React-Router-Bibliothek in der Komponente nutzen zu können, ordnen wir die dispatch einfach dem Repository zu und tun dies entsprechend den Anforderungen der Anwendung. Hier ist ein Beispiel für Code, der die Verwendung der Bibliothek für verbundene Reaktionsrouter demonstriert (damit dieser Code funktioniert, muss der Rest des Systems für die Verwendung von Routern für verbundene Reaktionen konfiguriert sein).

 import { push } from 'connected-react-router' ... const mapDispatchToProps = dispatch => ({  goTo: payload => {    dispatch(push(payload.path));  }, }); class DemoComponent extends React.Component {  render() {    return (      <Child        onClick={          () => {            this.props.goTo({ path: `/gallery/`});                      />    } ... 

Hier goTo die goTo Methode eine Aktion aus, die die erforderliche URL goTo Browserverlaufsstapel goTo . Zuvor wurde die goTo Methode dem Repository zugeordnet. Daher wird diese Methode an die DemoComponent im props .

Dynamische Benutzeroberfläche und Anforderungen einer wachsenden Anwendung


Im Laufe der Zeit wirken sich trotz des Vorhandenseins eines angemessenen Backends für die Anwendung und eines hochwertigen Client-Teils einige Elemente der Benutzeroberfläche stark auf die Arbeit des Benutzers aus. Dies liegt an der irrationalen Implementierung der Komponenten, die auf den ersten Blick sehr einfach erscheint. In diesem Abschnitt werden Empfehlungen zum Erstellen einiger Widgets erläutert. Ihre korrekte Implementierung wird mit zunehmender Anwendung der Anwendung komplizierter.

▍ Faules Laden und React.Suspense


Das Beste an der Asynchronität von JavaScript ist, dass es das volle Potenzial des Browsers ausnutzt. Vielleicht ist das wahre Gute, dass Sie zum Starten eines bestimmten Prozesses nicht auf den Abschluss der vorherigen Aufgabe warten müssen. Entwickler können jedoch das Netzwerk und die Geschwindigkeit, mit der die verschiedenen für das Funktionieren der Websites erforderlichen Materialien geladen werden, nicht beeinflussen.

Netzwerksubsysteme werden normalerweise als unzuverlässig und fehleranfällig empfunden.

Um seine Anwendung so hochwertig wie möglich zu gestalten, kann der Entwickler sie zahlreichen Prüfungen unterziehen und ihre erfolgreiche Passage erreichen. Dennoch gibt es einige Dinge, wie den Status der Netzwerkverbindung oder die Serverantwortzeit, die der Entwickler nicht beeinflussen kann.

Die Entwickler der Software versuchen jedoch nicht, die minderwertige Arbeit von Anwendungen mit Sätzen wie "Das geht mich nichts an" zu rechtfertigen. Sie fanden interessante Wege, um mit Netzwerkproblemen umzugehen.

In einigen Teilen der Front-End-Anwendung müssen Sie möglicherweise einige Sicherungsmaterialien anzeigen (z. B. viel schneller als echte Materialien laden). Dies erspart dem Benutzer das Nachdenken über das "Zucken" der Ladeseiten oder, noch schlimmer, über solche Symbole.


Benutzer sind besser dran, so etwas nicht zu sehen.

Mit der React Suspense-Technologie können Sie genau solche Probleme bewältigen. So können Sie beispielsweise beim Laden von Daten einen bestimmten Indikator anzeigen. Obwohl dies auch manuell erfolgen kann, indem die isLoaded Eigenschaft auf true , macht die Verwendung der Suspense-API den Code viel sauberer.

Hier können Sie ein gutes Video über Suspense sehen, in dem Jared Palmer das Publikum in diese Technologie einführt und einige ihrer Funktionen am Beispiel einer realen Anwendung zeigt .

So funktioniert die App ohne Verwendung von Suspense.


Anwendung, bei der Suspense nicht verwendet wird

Das Ausstatten einer Komponente mit Suspense-Unterstützung ist viel einfacher als die Verwendung von anwendungsweitem isLoaded . Beginnen wir damit, den übergeordneten App Container in React.StrictMode . Wir stellen sicher, dass es unter den in der Anwendung verwendeten React-Modulen keine gibt, die als veraltet gelten.

 <React.Suspense fallback={<Spinner size="large" />}>  <ArtistDetails id={this.props.id}/>  <ArtistTopTracks />  <ArtistAlbums id={this.props.id}/> </React.Suspense> 

In React.Suspense Tags eingeschlossene Komponenten React.Suspense während des Ladens des Hauptinhalts die in der fallback Eigenschaft angegebenen Komponenten und zeigen sie an. Wir müssen uns bemühen, sicherzustellen, dass die in der fallback Eigenschaft verwendeten Komponenten das kleinstmögliche Volumen haben und so einfach wie möglich angeordnet sind.


Anwendung, die Spannung verwendet

▍Anpassende Komponenten


In großen Front-End-Anwendungen kommt es häufig zu sich wiederholenden Mustern. Gleichzeitig kann dies zu Beginn der Arbeit fast völlig offensichtlich sein. Es gibt nichts zu tun, aber Sie müssen darauf gestoßen sein.

Zum Beispiel gibt es zwei Modelle in der Anwendung. Eine davon dient zur Beschreibung von Rennstrecken und die zweite zur Beschreibung von Autos. Die Fahrzeuglistenseite verwendet quadratische Elemente. Jeder von ihnen enthält ein Bild und eine kurze Beschreibung.

Die Trace-Liste verwendet ähnliche Elemente. Ihr Hauptmerkmal ist, dass sie neben dem Bild und der Beschreibung der Strecke auch ein kleines Feld haben, das angibt, ob es den Zuschauern eines auf dieser Strecke stattfindenden Rennens möglich ist, etwas zu essen zu kaufen.


Element für die Beschreibung des Autos und Element für die Beschreibung der Strecke

Diese beiden Komponenten unterscheiden sich hinsichtlich des Stils geringfügig voneinander (sie haben unterschiedliche Hintergrundfarben). Die Komponente, die die Route beschreibt, enthält einige zusätzliche Informationen zu dem von ihr beschriebenen realen Objekt, während die Komponente, die das Auto symbolisiert, keine solchen Informationen enthält. Dieses Beispiel zeigt nur zwei Modelle. In einer großen Anwendung können viele ähnliche Modelle eingegeben werden, die sich nur in kleinen Dingen unterscheiden.

Die Schaffung separater unabhängiger Komponenten für jede dieser Einheiten widerspricht dem gesunden Menschenverstand.

Ein Programmierer kann sich die Notwendigkeit ersparen, Codefragmente zu schreiben, die sich fast vollständig wiederholen. Dies kann durch die Entwicklung adaptiver Komponenten erfolgen. Sie berücksichtigen im Laufe der Arbeit die Umgebung, in die sie geladen wurden. Betrachten Sie die Suchleiste einer bestimmten Anwendung.


Suchleiste

Es wird auf vielen Seiten verwendet. Gleichzeitig werden geringfügige Änderungen an seinem Erscheinungsbild und der Reihenfolge seiner Arbeit auf verschiedenen Seiten vorgenommen. Auf der Homepage des Projekts ist sie beispielsweise etwas größer als auf anderen Seiten. Um dieses Problem zu lösen, können Sie eine einzelne Komponente erstellen, die gemäß den an sie übertragenen Eigenschaften angezeigt wird.

 static propTypes = {  open: PropTypes.bool.isRequired,  setOpen: PropTypes.func.isRequired,  goTo: PropTypes.func.isRequired, }; 

Mit dieser Technik können Sie die Verwendung von HTML-Klassen beim Rendern solcher Elemente steuern, wodurch Sie deren Erscheinungsbild beeinflussen können.

Eine weitere interessante Situation, in der adaptive Komponenten Anwendung finden können, ist der Mechanismus zum Aufteilen bestimmter Materialien in Seiten. Auf jeder Seite der Anwendung kann eine Navigationsleiste vorhanden sein. Die Instanzen dieses Bedienfelds auf jeder Seite sind fast genau die gleichen wie auf den anderen Seiten.


Paginierungsfeld

Angenommen, eine bestimmte Anwendung benötigt ein ähnliches Bedienfeld. Bei der Arbeit an dieser Anwendung halten sich die Entwickler an die aktuellen Anforderungen. In einer solchen Situation muss die adaptive Komponente, die zum Aufteilen des Materials in Seiten verwendet wird, nur einige Eigenschaften übergeben. Dies ist die URL und die Anzahl der Elemente pro Seite.

Zusammenfassung


Das React-Ökosystem ist heutzutage so ausgereift, dass es unwahrscheinlich ist, dass jemals jemand in irgendeiner Phase der Anwendungsentwicklung ein Fahrrad erfinden muss. Obwohl dies den Entwicklern in die Hände spielt, führt dies dazu, dass es schwierig wird, genau zu wählen, was für jedes spezifische Projekt gut funktioniert.

Jedes Projekt ist in Bezug auf Umfang und Funktionalität einzigartig. Es gibt keinen einheitlichen Ansatz oder eine universelle Regel für die Entwicklung von React-Anwendungen. Daher ist es vor Beginn der Entwicklung wichtig, diese korrekt zu planen.

Bei der Planung ist es sehr leicht zu verstehen, welche Tools direkt für das Projekt erstellt werden und welche eindeutig nicht für ihn geeignet sind, da sie zu groß für ihn sind. Beispielsweise benötigt eine Anwendung, die aus 2-3 Seiten besteht und nur sehr wenige Anforderungen an bestimmte APIs ausführt, keine Datenspeicher, die denen ähneln, über die wir gesprochen haben. Ich bin bereit, in diesen Überlegungen noch weiter zu gehen und zu sagen, dass Sie in kleinen Projekten Redux nicht verwenden müssen.
In der Planungsphase der Anwendung ist beim Zeichnen von Layouts ihrer Seiten leicht zu erkennen, dass auf diesen Seiten viele ähnliche Komponenten verwendet werden. Wenn Sie versuchen, den Code solcher Komponenten wiederzuverwenden oder universelle Komponenten zu schreiben, können Sie viel Zeit und Mühe sparen.

Abschließend möchte ich darauf hinweisen, dass Daten der Kern jeder Anwendung sind. Und React-Anwendungen sind keine Ausnahme. Wenn der Umfang der Anwendung zunimmt, wächst das Volumen der verarbeiteten Daten, und es erscheinen zusätzliche Softwaremechanismen für die Arbeit mit ihnen. So etwas kann, wenn die Anwendung schlecht gestaltet war, Programmierer leicht „zerquetschen“ und sie mit komplexen und verwirrenden Aufgaben füllen. Wenn im Verlauf der Planung die Probleme bei der Verwendung von Data Warehouses im Voraus entschieden wurden, wenn die Reihenfolge der Arbeit von Aktionen, Reduzierungen und Durchhängen im Voraus festgelegt wurde, wäre die Arbeit an der Anwendung viel einfacher.

Liebe Leser! Wenn Sie Bibliotheken oder Entwicklungsmethoden kennen, die beim Erstellen umfangreicher React-Anwendungen eine gute Leistung erbringen, teilen Sie diese bitte mit.

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


All Articles