Skalieren einer Redux-App mit Enten

Im Vorfeld des Kursbeginns erstellte "React.js Entwickler" eine Übersetzung von nützlichem Material.





Wie skaliert das Frontend Ihrer Anwendung? Wie können Sie Ihren Code sechs Monate später unterstützen?

Im Jahr 2015 stürmte Redux die Front-End-Entwicklungswelt und etablierte sich als Standard jenseits von React.

Das Unternehmen, für das ich arbeite, hat kürzlich die Umgestaltung einer großen Codebasis auf React abgeschlossen, bei der wir Redux anstelle von Reflux implementiert haben .

Wir mussten diesen Schritt tun, da ein Fortschritt ohne eine gut strukturierte Anwendung und ein klares Regelwerk nicht möglich war.

Die Codebasis gibt es seit mehr als zwei Jahren und Reflux war von Anfang an dabei. Wir mussten den Code ändern, der stark an React-Komponenten gebunden war, die seit mehr als einem Jahr niemand mehr berührt hatte.
Basierend auf den Erfahrungen aus der geleisteten Arbeit habe ich dieses Repository erstellt , um unseren Ansatz zur Organisation von Code auf Redux zu erläutern.
Wenn Sie mehr über Redux, Aktionen und Reduzierungen erfahren , beginnen Sie mit einfachen Beispielen. Viele der heute verfügbaren Tutorials gehen nicht darüber hinaus. Wenn Sie jedoch in Redux etwas Komplexeres als eine Aufgabenliste erstellen, benötigen Sie eine vernünftige Methode, um Ihre Codebasis im Laufe der Zeit zu skalieren.

Jemand hat einmal gesagt, dass es in der Informatik keine schwierigere Aufgabe gibt, als verschiedene Dinge zu benennen. Ich konnte nicht widersprechen. In diesem Fall stehen die Ordnerstrukturierung und die Dateiorganisation an zweiter Stelle.

Schauen wir uns an, wie wir uns der Code-Organisation näherten.

Funktion gegen Funktion


Es gibt zwei allgemein anerkannte Ansätze zum Organisieren von Anwendungen: Funktion zuerst und Funktion zuerst.
Im Screenshot links ist die Ordnerstruktur nach dem Prinzip von Funktion zuerst und rechts nach Merkmal zuerst organisiert.



Function-first bedeutet, dass Ihre Verzeichnisse der obersten Ebene nach den darin enthaltenen Dateien benannt werden. Sie haben also: Container, Komponenten, Aktionen, Reduzierstücke usw.

Dies skaliert überhaupt nicht. Wenn Ihre Anwendung wächst und neue Funktionen angezeigt werden, fügen Sie Dateien denselben Ordnern hinzu. Infolgedessen müssen Sie lange durch den Inhalt eines der Ordner scrollen, um die gewünschte Datei zu finden.

Ein weiteres Problem ist das Kombinieren von Ordnern. Einer Ihrer Anwendungsthreads erfordert wahrscheinlich den Zugriff auf Dateien aus allen Ordnern.

Einer der Vorteile dieses Ansatzes besteht darin, dass er in unserem Fall React von Redux isolieren kann. Wenn Sie die Statusverwaltungsbibliothek ändern möchten, wissen Sie daher, welche Ordner Sie benötigen. Wenn Sie die Ansichtsbibliothek ändern müssen, können Sie die Ordner mit Redux intakt lassen.

Feature-First bedeutet, dass Verzeichnisse der obersten Ebene gemäß der Hauptfunktionalität der Anwendung benannt werden: Produkt, Warenkorb, Sitzung.

Dieser Ansatz lässt sich viel besser skalieren, da jedes neue Feature in einem neuen Ordner liegt. Sie haben jedoch keine Trennung zwischen den Komponenten Redux und React. Eine davon auf lange Sicht zu ändern, ist keine leichte Aufgabe.

Außerdem haben Sie Dateien, die keiner Funktion angehören. Infolgedessen kommt es auf den gemeinsamen oder freigegebenen Ordner an, da Sie Ihren Code auch in verschiedenen Funktionen Ihrer Anwendung verwenden möchten.

Das Beste aus zwei Welten kombinieren


Obwohl dies nicht das Thema des Artikels ist, möchte ich sagen, dass die Statusverwaltungsdateien aus den UI-Dateien separat gespeichert werden müssen.

Denken Sie langfristig an Ihre Anwendung. Stellen Sie sich vor, was mit Ihrem Code passiert, wenn Sie von Reagieren zu etwas anderem wechseln. Oder überlegen Sie, wie Ihre Codebasis ReactNative parallel zur Webversion verwendet.

Im Zentrum unseres Ansatzes steht das Prinzip der Isolation. Reagieren Sie Code in einem Ordner namens Ansichten und Code Redux in einem anderen Ordner namens Redux.

Diese Trennung auf der Einstiegsebene gibt uns die Flexibilität, die einzelnen Teile der Anwendung auf völlig unterschiedliche Weise zu organisieren.

Im AnsichtsordnerWir unterstützen die Organisation von Function-First-Dateien. Im Kontext von React sieht dies natürlich aus: Seiten, Layouts, Komponenten, Enhancer usw.

Um nicht verrückt nach der Anzahl der Dateien in einem Ordner zu werden, können Sie den Feature-First-Ansatz in diesen Ordnern verwenden.

Inzwischen im Redux- Ordner ...

Einführung von Re-Enten


Jede Anwendungsfunktion muss separaten Aktionen und Reduzierungen entsprechen, damit es sinnvoll ist, den Feature-First-Ansatz anzuwenden.

Die originalen modularen Ansatzenten erleichtern das Arbeiten mit Redux und bieten eine strukturierte Möglichkeit, Ihrer Anwendung neue Funktionen hinzuzufügen.

Sie wollten jedoch verstehen, was passiert, wenn Sie die Anwendung skalieren. Wir haben festgestellt, dass die Art und Weise, eine Datei pro Feature zu organisieren, die Anwendung überfüllt und ihre Unterstützung problematisch macht.

Also tauchten Re-Enten auf . Die Lösung bestand darin, die Funktionalität in Entenordner aufzuteilen.

duck/
├── actions.js
├── index.js
├── operations.js
├── reducers.js
├── selectors.js
├── tests.js
├── types.js
├── utils.js

Der Entenordner sollte:

  • Enthalten Sie die gesamte Logik, nur EIN Konzept Ihrer Anwendung zu verarbeiten, z. B. Produkt, Warenkorb, Sitzung usw.
  • Enthält die Datei index.js, die gemäß den Entenregeln exportiert wird.
  • Speichern Sie Code in einer einzelnen Datei, die ähnliche Aufgaben ausführt, z. B. Reduzierungen, Selektoren und Aktionen.
  • Enthalten Tests in Bezug auf Ente.

In diesem Beispiel haben wir beispielsweise keine Abstraktionen verwendet, die auf Redux basieren. Bei der Erstellung von Software ist es wichtig, mit dem geringsten Abstraktionsgrad zu beginnen. Sie werden also sehen, dass der Wert Ihrer Abstraktionen die Vorteile dieser Abstraktionen nicht übersteigt.

Wenn Sie sicherstellen möchten, dass die Abstraktion schlecht ist, sehen Sie sich dieses Video von Cheng Lou an .

Schauen wir uns den Inhalt jeder Datei an.

Typen


Die Typen - Datei enthält die Namen der Aktionen , die Sie in Ihrer Anwendung auszuführen. Als gute Vorgehensweise sollten Sie versuchen, den Namespace abzudecken, der der Funktion entspricht, zu der sie gehören. Dieser Ansatz hilft beim Debuggen komplexer Anwendungen.

const QUACK = "app/duck/QUACK";
const SWIM = "app/duck/SWIM";

export default {
    QUACK,
    SWIM
};

Aktionen


Diese Datei enthält alle Funktionen des Aktionserstellers .

import types from "./types";

const quack = ( ) => ( {
    type: types.QUACK
} );

const swim = ( distance ) => ( {
    type: types.SWIM,
    payload: {
        distance
    }
} );

export default {
    swim,
    quack
};

Beachten Sie, dass alle Aktionen durch Funktionen dargestellt werden, auch wenn sie nicht parametrisiert sind. Ein konsistenter Ansatz hat für eine große Codebasis die höchste Priorität.

Operationen


Um die Kette von Operationen ( die Operationen ) darzustellen , benötigen Sie die Redux- Middleware , um die Funktion des Versands zu verbessern . Beliebte Beispiele sind Redux-Thunk , Redux-Saga oder Redux-Observable .

In unserem Fall wird Redux-Thunk verwendet . Wir müssen Thunks von Action-Erstellern trennen , selbst wenn wir zusätzlichen Code schreiben müssen. Daher definieren wir die Operation als Wrapper über Aktionen .

Wenn die Operation nur eine Aktion sendet , dh kein Redux-Thunk verwendet , senden wir die Funktion zum Erstellen von Aktionen. Wenn die Operation Thunk verwendet , kann sie viele Aktionen senden und diese mithilfe von Versprechungen verknüpfen .

import actions from "./actions";

// This is a link to an action defined in actions.js.
const simpleQuack = actions.quack;

// This is a thunk which dispatches multiple actions from actions.js
const complexQuack = ( distance ) => ( dispatch ) => {
    dispatch( actions.quack( ) ).then( ( ) => {
        dispatch( actions.swim( distance ) );
        dispatch( /* any action */ );
    } );
}

export default {
    simpleQuack,
    complexQuack
};

Nennen Sie sie Operationen, Thunks , Sagen, Epen, wie Sie wollen. Identifizieren Sie einfach die Prinzipien der Benennung und halten Sie sich an diese.

Ganz am Ende werden wir über den Index sprechen und sehen, dass Operationen Teil der öffentlichen Schnittstelle von duck sind. Aktionen sind gekapselt, Operationen werden von außen zugänglich.

Reduzierstücke


Wenn Sie eine vielfältigere Funktion haben, sollten Sie auf jeden Fall mehrere Reduzierungen verwenden , um komplexe Zustandsstrukturen zu handhaben. Haben Sie außerdem keine Angst davor, so viele Mähdrescher zu verwenden, wie Sie benötigen. Dies ermöglicht ein freieres Arbeiten mit Zustandsobjektstrukturen.

import { combineReducers } from "redux";
import types from "./types";

/* State Shape
{
    quacking: bool,
    distance: number
}
*/

const quackReducer = ( state = false, action ) => {
    switch( action.type ) {
        case types.QUACK: return true;
        /* ... */
        default: return state;
    }
}

const distanceReducer = ( state = 0, action ) => {
    switch( action.type ) {
        case types.SWIM: return state + action.payload.distance;
        /* ... */
        default: return state;
    }
}

const reducer = combineReducers( {
    quacking: quackReducer,
    distance: distanceReducer
} );

export default reducer;

In einer großen Anwendung besteht der Statusbaum aus mindestens drei Ebenen. Die Reduzierungsfunktionen sollten so klein wie möglich sein und nur einfache Datenkonstrukte verarbeiten. Die Funktion combinReducers ist alles, was Sie benötigen, um eine flexible und wartbare Statusstruktur zu erstellen.

Schauen Sie sich das vollständige Projektbeispiel an und erfahren Sie , wie Sie combinReducers richtig verwenden , insbesondere in Dateien reducers.jsund store.jswo wir den Statusbaum erstellen .

Selektoren


Zusammen mit dem Operations Selector ( Selector ) sind Teil der öffentlichen Schnittstelle Ente. Der Unterschied zwischen Operationen und Selektoren ähnelt dem CQRS- Muster .

Auswahlfunktionen nehmen einen Teil des Anwendungsstatus und geben einige darauf basierende Daten zurück. Sie nehmen niemals Änderungen am Status der Anwendung vor.

function checkIfDuckIsInRange( duck ) {
    return duck.distance > 1000;
}

export default {
    checkIfDuckIsInRange
};

Index


Diese Datei gibt an, was aus dem Entenordner exportiert wird.
Ist er:

  • Exportiert standardmäßig die Reduzierungsfunktion von duck.
  • Exportiert Selektoren und Operationen als registrierte Exporte.
  • Exportiert Typen, falls erforderlich, in andere Enten.

import reducer from "./reducers";

export { default as duckSelectors } from "./selectors";
export { default as duckOperations } from "./operations";
export { default as duckTypes } from "./types";

export default reducer;

Tests


Der Vorteil der Verwendung von Redux mit dem Enten-Framework besteht darin, dass Sie Tests direkt nach dem Code schreiben können, den Sie testen möchten.

Das Testen Ihres Codes auf Redux ist ziemlich einfach:

import expect from "expect.js";
import reducer from "./reducers";
import actions from "./actions";

describe( "duck reducer", function( ) {
    describe( "quack", function( ) {
        const quack = actions.quack( );
        const initialState = false;

        const result = reducer( initialState, quack );

        it( "should quack", function( ) {
            expect( result ).to.be( true ) ;
        } );
    } );
} );

In diese Datei können Sie Tests für Reduzierungen , Operationen, Selektoren usw. schreiben .
Ich könnte einen separaten Artikel über die Vorteile des Codetests schreiben, aber sie reichen bereits aus. Testen Sie also einfach Ihren Code!

Das ist alles


Die gute Nachricht bei Re-Ducks ist, dass Sie für Ihren gesamten Redux-Code dieselbe Vorlage verwenden können.

Der funktionsbasierte Partitionierungsansatz für Ihren Redux-Code hilft Ihrer Anwendung, flexibel und skalierbar zu bleiben, wenn sie wächst. Ein funktionsbasierter Trennungsansatz eignet sich gut, wenn kleine Komponenten erstellt werden, die verschiedenen Teilen der Anwendung gemeinsam sind.

Sie können an den vollen einen Blick reagieren-redux-Beispiel Codebasis hier . Beachten Sie, dass das Repository aktiv arbeitet.

Wie organisieren Sie Ihre Redux-Anwendungen? Ich freue mich auf Feedback zum beschriebenen Ansatz.

Wir sehen uns auf dem Kurs .

All Articles