Detaillierte Erklärung des JavaScript-Statuscontainers Redux

Detaillierte Erklärung des JavaScript-Statuscontainers Redux

1. Warum Redux

Bevor wir darüber sprechen, warum man Redux verwenden sollte, sprechen wir über die Art und Weise, wie Komponenten kommunizieren. Die gängigen Methoden zur Komponentenkommunikation sind die folgenden:

  • Übergeordnete und untergeordnete Komponenten: Requisiten, Status/Rückrufe zur Kommunikation
  • Single Page Application: Routing-Werte
  • Globale Ereignisse wie EventEmitter Listener Callback Wertübertragung
  • Kontext (Kontext) der komponentenübergreifenden Datenübertragung in React

In kleinen, weniger komplexen Anwendungen sind die oben genannten Methoden zur Komponentenkommunikation im Allgemeinen ausreichend.

Da Anwendungen jedoch immer komplexer werden, gibt es zu viele Datenzustände (wie Serverantwortdaten, Browser-Cachedaten, UI-Statuswerte usw.) und die Zustände können sich häufig ändern. Die Verwendung der oben genannten Komponentenkommunikationsmethode ist kompliziert, umständlich und es ist schwierig, damit verbundene Probleme zu lokalisieren und zu debuggen.

Daher sind Zustandsverwaltungsframeworks (wie Vuex, MobX, Redux usw.) sehr wichtig, und Redux ist das am weitesten verbreitete und umfassendste unter ihnen.

2. Redux-Datenfluss

In einer App, die Redux verwendet, werden die folgenden vier Schritte ausgeführt:

Schritt 1: Lösen Sie eine Aktion über store.dispatch(action) aus, wobei „action“ ein Objekt ist, das beschreibt, was passieren wird. wie folgt:

{ Typ: 'LIKE_ARTICLE', Artikel-ID: 42 }
{ Typ: 'FETCH_USER_SUCCESS', Antwort: { ID: 3, Name: 'Mary' } }
{ Typ: 'ADD_TODO', Text: 'Finanzielles Frontend.' }

Schritt 2: Redux ruft die von Ihnen bereitgestellte Reducer-Funktion auf.

Schritt 3: Der Root-Reducer führt mehrere verschiedene Reducer-Funktionen zu einem einzigen Zustandsbaum zusammen.

Schritt 4: Der Redux-Speicher speichert den vollständigen Statusbaum, der von der Stammfunktion „Reducer“ zurückgegeben wird.

Wie das Sprichwort sagt: Ein Bild sagt mehr als tausend Worte. Lassen Sie uns das Redux-Datenflussdiagramm verwenden, um uns mit diesem Prozess vertraut zu machen.

Drei Prinzipien

1. Einzige Quelle der Wahrheit: Eine einzige Datenquelle, der Status der gesamten Anwendung wird in einem Objektbaum gespeichert und existiert nur in einem Speicher.

2. Status ist schreibgeschützt: Der Status im Status ist schreibgeschützt. Sie können den Status nicht direkt ändern. Sie können nur durch Auslösen einer Aktion einen neuen Status zurückgeben.

3. Änderungen werden mit reinen Funktionen vorgenommen: Verwenden Sie reine Funktionen, um den Status zu ändern.

4. Redux-Quellcode-Analyse

Der Redux-Quellcode verfügt derzeit über JS- und TS-Versionen. Dieser Artikel stellt zunächst die JS-Version des Redux-Quellcodes vor. Da die Anzahl der Quellcodezeilen von Redux nicht groß ist, lohnt es sich für Entwickler, die ihre Fähigkeit zum Lesen von Quellcode verbessern möchten, dies frühzeitig zu lernen.

Der Redux-Quellcode ist hauptsächlich in 6 Core-JS-Dateien und 3 Tool-JS-Dateien unterteilt. Die Core-JS-Dateien sind die Dateien index.js, createStore.js, compose.js, combineRuducers.js, bindActionCreators.js und applyMiddleware.js.

Als nächstes wollen wir sie einzeln untersuchen.

4.1, index.js

index.js ist die Einstiegsdatei, die Kern-APIs wie createStore, combineReducers, applyMiddleware usw. bereitstellt.

exportieren {
  erstellenShop,
  kombinierenReduzierer,
  bindActionCreators,
  applyMiddleware,
  komponieren,
  __ActionTypes_NICHT_VERWENDEN__
}

4.2, createStore.js

createStore ist eine von Redux bereitgestellte API zum Generieren eines einzigartigen Stores. Der Store stellt Methoden wie getState, dispatch und subscribe bereit. Der Store in Redux kann nur eine Aktion versenden und über die Aktion die entsprechende Reducer-Funktion finden, um Änderungen vorzunehmen.

Exportiere Standardfunktion createStore(Reducer, PreloadedState, Enhancer) {
...
}

Aus dem Quellcode können wir erkennen, dass createStore drei Parameter empfängt: Reducer, preloadedState und enhancer.

Reducer ist eine reine Funktion, die einer Aktion entspricht, die den Status im Store ändern kann.

preloadedState stellt den Initialisierungszustand des vorherigen Zustands dar.

Enhancer ist eine erweiterte Funktion, die von der Middleware durch applyMiddleware generiert wird. Die Methode getState im Store wird verwendet, um den Statusbaum im Store der aktuellen Anwendung abzurufen.

/**
 * Liest den vom Store verwalteten Statusbaum.
 *
 * @returns {any} Der aktuelle Statusbaum Ihrer Anwendung.
 */
Funktion getState() {
  wenn (istVersand) {
    neuen Fehler werfen (
      "Sie dürfen store.getState() nicht aufrufen, während der Reducer ausgeführt wird. ' +
        'Der Reducer hat den Status bereits als Argument erhalten. ' +
        „Geben Sie es vom oberen Reduzierer weiter, anstatt es im Laden zu lesen.“
    )
  }
  Aktuellen Status zurückgeben
}

Zum Versenden einer Aktion wird die Dispatch-Methode verwendet. Sie ist die einzige Methode, die eine Statusänderung auslösen kann. „subscribe“ ist ein Listener, der aufgerufen wird, wenn eine Aktion ausgeführt wird oder sich ein Status ändert.

4.3. kombinierenReducers.js

/**
 * Wandelt ein Objekt, dessen Werte verschiedene Reducer-Funktionen sind, in ein einziges um
 * Reducer-Funktion. Sie ruft alle untergeordneten Reducer auf und sammelt deren Ergebnisse.
 * in ein einzelnes Statusobjekt, dessen Schlüssel den Schlüsseln der übergebenen
 * Reduzierungsfunktionen.
 */
Export-Standardfunktion combineReducers(Reducer) {
  const ReducerKeys = Objekt.keys(Reducer)
     ...
  Rückgabefunktionskombination (Status = {}, Aktion) {
     ...
    let hat sich geändert = false
    const nächsterState = {}
    für (lass i = 0; i < finalReducerKeys.length; i++) {
      Konstantschlüssel = finalReducerKeys[i]
      const Reducer = finalReducers[Schlüssel]
      const previousStateForKey = Status[Schlüssel]
      const nextStateForKey = Reducer (vorherigerStateForKey, Aktion)
      wenn (Typ von nextStateForKey === 'undefiniert') {
        const errorMessage = getUndefinedStateErrorMessage(Schlüssel, Aktion)
        neuen Fehler werfen (Fehlermeldung)
      }
      nächsterZustand[Schlüssel] = nächsterZustandFürSchlüssel
      //Feststellen, ob sich der Status geändert hat hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    //Entscheiden Sie, ob der neue oder der alte Status zurückgegeben werden soll, je nachdem, ob eine Änderung aufgetreten ist
    returniert hat sich geändert? nächsterZustand: Zustand
  }
}

Aus dem Quellcode können wir erkennen, dass der Eingabeparameter Reducers ist und eine Funktion zurückgibt. combineReducers kombiniert alle Reducer zu einer großen Reducer-Funktion. Der entscheidende Punkt ist, dass der Reducer jedes Mal, wenn er einen neuen Status zurückgibt, diesen mit dem alten Status vergleicht. Wenn eine Änderung vorliegt, ist hasChanged wahr und löst eine Seitenaktualisierung aus. Andernfalls erfolgt keine Bearbeitung.

4.4.bindActionCreators.js

/**
 * Verwandelt ein Objekt, dessen Werte Aktionsersteller sind, in ein Objekt mit dem
 * dieselben Tasten, aber jede Funktion ist in einen `dispatch`-Aufruf eingebunden, so dass sie
 * kann direkt aufgerufen werden. Dies ist nur eine praktische Methode, da Sie
 * `store.dispatch(MyActionCreators.doSomething())` selbst ist problemlos möglich.
 */
Funktion bindActionCreator(actionCreator, dispatch) {
  return Funktion() {
    returniere dispatch(actionCreator.apply(diese, Argumente))
  }
}
 
exportiere Standardfunktion bindActionCreators(actionCreators, dispatch) {
  wenn (Typ von Aktionserstellern === 'Funktion') {
    returniere bindActionCreator(actionCreators, dispatch)
  }
    ...
    ...
  const keys = Objekt.keys(actionCreators)
  const boundActionCreators = {}
  für (lass i = 0; i < Schlüssel.Länge; i++) {
    const Schlüssel = Schlüssel[i]
    const actionCreator = actionCreators[Schlüssel]
    wenn (Typ von Aktionsersteller === 'Funktion') {
      boundActionCreators[Schlüssel] = bindActionCreator(actionCreator, dispatch)
    }
  }
  returniere gebundeneActionCreators
}

bindActionCreator bindet einen einzelnen ActionCreator an Dispatch, und bindActionCreators bindet mehrere ActionCreators an Dispatch.

bindActionCreator vereinfacht das Senden von Aktionen. Wenn die zurückgegebene Funktion aufgerufen wird, wird dispatch automatisch aufgerufen, um die entsprechende Aktion zu senden.

bindActionCreators führt je nach ActionCreator-Typ unterschiedliche Verarbeitungen durch. Wenn ActionCreators eine Funktion ist, gibt es eine Funktion zurück; wenn es ein Objekt ist, gibt es ein Objekt zurück. Der Hauptzweck besteht darin, Aktionen in das Dispatch-Format (Aktionsformat) zu konvertieren, um die Trennung von Aktionen zu erleichtern und den Code prägnanter zu gestalten.

4.5.compose.js

/**
 * Setzt einargumentige Funktionen von rechts nach links zusammen. Das am weitesten rechts
 * Funktion kann mehrere Argumente annehmen, da sie die Signatur für bereitstellt
 * die resultierende zusammengesetzte Funktion.
 *
 * @param {...Function} funcs Die zu erstellenden Funktionen.
 * @returns {Function} Eine Funktion, die durch Zusammensetzen der Argumentfunktionen erhalten wird
 * von rechts nach links. Beispielsweise ist compose(f, g, h) identisch mit
 * (...args) => f(g(h(...args))).
 */
 
exportiere Standardfunktion compose(...funcs) {
  wenn (funcs.length === 0) {
    return arg => arg
  }
 
  wenn (funcs.length === 1) {
    Rückgabefunktionen[0]
  }
 
  Rückgabefunktionen.reduce((a, b) => (...args) => a(b(...args)))
}

Compose ist ein sehr wichtiges Konzept in der funktionalen Programmierung. Bevor wir Compose vorstellen, wollen wir zunächst verstehen, was Reduce ist. Die offizielle Dokumentation definiert „reduce“ wie folgt: Die Methode „reduce()“ wendet eine Funktion auf den Akkumulator und jedes Element im Array (von links nach rechts) an, um es zu einem Wert zu vereinfachen. Compose ist eine Curry-Funktion, die mit Hilfe von Reduce implementiert wird. Sie kombiniert mehrere Funktionen zu einer Funktion, die zurückgegeben werden soll. Sie wird hauptsächlich in Middleware verwendet.

4.6.applyMiddleware.js

/**
 * Erstellt einen Store-Enhancer, der Middleware auf die Dispatch-Methode anwendet
 * des Redux-Stores. Dies ist praktisch für eine Vielzahl von Aufgaben, wie zum Beispiel das Ausdrücken
 * asynchrone Aktionen in prägnanter Weise oder Protokollieren der Nutzlast jeder Aktion.
 */
Exportieren Sie die Standardfunktion applyMiddleware(...middlewares) {
  returniere createStore => (...args) => {
    const store = createStore(...args)
    ...
    ...
    zurückkehren {
      ...speichern,
      versenden
    }
  }
}

Die Datei applyMiddleware.js stellt die wichtige API der Middleware bereit. Middleware wird hauptsächlich verwendet, um store.dispatch neu zu schreiben, um die Dispatch-Funktion zu verbessern und zu erweitern.

Warum brauchen wir also Middleware?

Zunächst müssen wir mit Reducer beginnen. Die drei Prinzipien von Redux besagen, dass Reducer eine reine Funktion sein muss. Hier ist die Definition einer reinen Funktion:

  • Für dieselben Parameter wird dasselbe Ergebnis zurückgegeben
  • Das Ergebnis hängt vollständig von den übergebenen Parametern ab.
  • Keine Nebenwirkungen

Warum der Reducer eine reine Funktion sein muss, können wir von den folgenden Punkten ausgehen?

  • Da Redux ein vorhersehbarer Statusmanager ist, erleichtern reine Funktionen das Debuggen von Redux, das Verfolgen und Lokalisieren von Problemen und verbessern die Entwicklungseffizienz.
  • Redux vergleicht lediglich die Adressen der neuen und alten Objekte, um festzustellen, ob sie gleich sind, und führt daher einen oberflächlichen Vergleich durch. Wenn Sie den Eigenschaftswert des alten Status direkt im Reducer ändern, verweisen sowohl das alte als auch das neue Objekt auf dasselbe Objekt. Wenn Sie weiterhin einen oberflächlichen Vergleich verwenden, geht Redux davon aus, dass keine Änderung aufgetreten ist. Wenn dies jedoch durch einen ausführlichen Vergleich geschieht, ist die Leistung sehr hoch. Am besten ist es, wenn Redux ein neues Objekt zurückgibt und die neuen und alten Objekte oberflächlich vergleicht. Dies ist auch ein wichtiger Grund dafür, dass Reducer eine reine Funktion ist.

Reducer ist eine reine Funktion, aber in der Anwendung müssen dennoch Vorgänge wie Protokollierung/Ausnahmen und asynchrone Verarbeitung behandelt werden. Wie lassen sich diese Probleme lösen?

Die Antwort auf dieses Problem ist Middleware. Die Dispatch-Funktion kann durch Middleware erweitert werden. Nachfolgend ein Beispiel (Protokollierung und Ausnahmeaufzeichnung):

Konstantenspeicher = ErstelleSpeicher(Reducer);
const next = speichern.dispatch;
 
// store.dispatch überschreiben
store.dispatch = (Aktion) => {
    versuchen {
        console.log('Aktion:', Aktion);
        console.log('aktueller Status:', store.getState());
        nächste(Aktion);
        console.log('nächster Status', store.getState());
    } Fehler abfangen {
        console.error('msg:', Fehler);
    }
}

5. Implementieren Sie ein einfaches Redux von Grund auf

Da wir einen Redux (einfachen Zähler) von Grund auf neu implementieren werden, vergessen wir die zuvor erwähnten Konzepte von Store, Reducer, Dispatch usw. Wir müssen nur daran denken, dass Redux ein Statusmanager ist.

Schauen wir uns zunächst den folgenden Code an:

lass Zustand = {
    Anzahl : 1
}
//Vor der Änderung console.log (state.count);
//Ändern Sie den Wert von count auf 2
Zustandsanzahl = 2;
//Nach der Änderung console.log (state.count);

Wir definieren ein Statusobjekt mit einem Zählfeld, das den Zählwert vor und nach der Änderung ausgeben kann. Aber an diesem Punkt stoßen wir auf ein Problem? Das heißt, andere Stellen, die auf die Zählung verweisen, wissen nicht, dass die Zählung geändert wurde. Daher müssen wir dies über das Abonnement-Veröffentlichungsmodell überwachen und andere Stellen, die auf die Zählung verweisen, benachrichtigen. Daher optimieren wir den Code wie folgt weiter:

lass Zustand = {
    Anzahl: 1
};
//Abonnieren-Funktion subscribe (listener) {
    listeners.push(listener);
}
Funktion changeState(Anzahl) {
    Zustand.Anzahl = Anzahl;
    für (lass i = 0; i < listeners.length; i++) {
        const listener = Listener[i];
        listener(); // hören}
}

Wenn wir zu diesem Zeitpunkt die Anzahl ändern, werden alle Zuhörer benachrichtigt und können entsprechende Maßnahmen ergreifen. Aber gibt es derzeit noch andere Probleme? Beispielsweise enthält der Status derzeit nur ein Zählfeld. Wenn mehrere Felder vorhanden sind, sind die Verarbeitungsmethoden konsistent? Gleichzeitig müssen wir auch berücksichtigen, dass der öffentliche Code weiter gekapselt werden muss. Als nächstes werden wir ihn weiter optimieren:

const createStore = Funktion (InitState) {
    lass Zustand = InitState;
    //Abonnieren-Funktion subscribe (listener) {
        listeners.push(listener);
    }
    Funktion changeState (Anzahl) {
        Zustand.Anzahl = Anzahl;
        für (lass i = 0; i < listeners.length; i++) {
            const listener = Listener[i];
            listener(); //Benachrichtigung}
    }
    Funktion getState () {
        Rückgabezustand;
    }
    zurückkehren {
        abonnieren,
        Zustand ändern,
        Status abrufen
    }
}

Wir können dem Code entnehmen, dass wir letztendlich drei APIs bereitstellen, die der Kerneintragsdatei index.js im vorherigen Redux-Quellcode ähneln. Redux wurde jedoch noch nicht implementiert. Wir müssen das Hinzufügen mehrerer Felder zum Status unterstützen und einen Redux-Zähler implementieren.

lass initState = {
    Schalter: {
        Anzahl : 0
    },
    Info:
        Name: '',
        Beschreibung: ''
    }
}
Lassen Sie uns speichern = createStore(initState);
//Anzahl ausgeben
speichern.abonnieren(()=>{
    let status = store.getState();
    Konsole.log(Zustand.Zähler.Anzahl);
});
// Ausgabeinformationen
speichern.abonnieren(()=>{
    let status = store.getState();
    console.log(`${state.info.name}:${state.info.description}`);
});

Durch Tests haben wir herausgefunden, dass es derzeit das Speichern mehrerer Attributfelder im Status unterstützt. Als Nächstes werden wir den vorherigen changeState ändern, um die Unterstützung von Selbstinkrement und Selbstdekrement zu ermöglichen.

//Selbstinkrementierung store.changeState({
    Anzahl: store.getState().count + 1
});
//Selbstdekrementieren store.changeState({
    Anzahl: store.getState().count - 1
});
//Zu irgendetwas ändern store.changeState({
    Anzahl: Finanzen});

Wir haben festgestellt, dass wir es über changeState beliebig erhöhen, verringern oder ändern können, aber das ist nicht das, was wir brauchen. Wir müssen die Änderung der Anzahl einschränken, da wir bei der Implementierung eines Zählers auf jeden Fall nur Additions- und Subtraktionsoperationen durchführen möchten. Daher werden wir changeState einschränken und uns auf eine Planmethode einigen, um je nach Typ unterschiedliche Verarbeitungen durchzuführen.

Funktionsplan (Zustand, Aktion) => {
  Schalter (Aktion.Typ) {
    Fall 'INCREMENT':
      zurückkehren {
        ...Zustand,
        Anzahl: Status.Anzahl + 1
      }
    Fall 'DECREMENT':
      zurückkehren {
        ...Zustand,
        Anzahl: Status.Anzahl - 1
      }
    Standard:
      Rückgabestatus
  }
}
Lassen Sie uns speichern = createStore(planen, initState);
//Selbstinkrementierung store.changeState({
    Typ: ‚INCREMENT‘
});
//Selbstdekrementieren store.changeState({
    Typ: ‚DECREMENT‘
});

Wir haben verschiedene Typen im Code unterschiedlich behandelt. Jetzt stellen wir fest, dass wir die Anzahl im Status nicht mehr beliebig ändern können. Wir haben changeState erfolgreich eingeschränkt. Wir verwenden die Planmethode als Eingabeparameter von createStore und führen sie beim Ändern des Status gemäß der Planmethode aus. Herzlichen Glückwunsch! Wir haben mit Redux einen einfachen Zähler implementiert.

Ist das Redux? Wie kommt es, dass dies vom Quellcode abweicht?

Dann ändern wir Plan in Reducer und ChangeState in Dispatch und stellen fest, dass dies die grundlegende Funktion ist, die vom Redux-Quellcode implementiert wird. Wenn wir jetzt noch einmal auf das Redux-Datenflussdiagramm zurückblicken, ist es klarer.

6. Redux-Entwicklertools

Redux devtools ist ein Debugging-Tool für Redux. Sie können das entsprechende Plug-in auf Chrome installieren. Bei mit Redux verbundenen Anwendungen können mit den Redux-Devtools die nach jeder Anforderung auftretenden Änderungen leicht erkannt werden. So können Entwickler Ursache und Wirkung jedes Vorgangs nachvollziehen und die Entwicklungs- und Debugging-Effizienz erheblich verbessern.

Wie in der Abbildung oben gezeigt, ist dies die visuelle Schnittstelle von Redux devtools. Die Bedienoberfläche auf der linken Seite ist die Aktion, die während des aktuellen Seiten-Rendering-Prozesses ausgeführt wird, und die Bedienoberfläche auf der rechten Seite sind die im Status gespeicherten Daten. Wechseln Sie vom Status- zum Aktionsbereich, um die der Aktion entsprechenden Reducer-Parameter anzuzeigen. Wechseln Sie zum Diff-Bedienfeld, um die Eigenschaftswerte anzuzeigen, die sich zwischen den beiden Vorgängen geändert haben.

VII. Fazit

Redux ist ein hervorragender State-Manager mit prägnantem Quellcode und einem ausgereiften Community-Ökosystem. Beispielsweise sind die häufig verwendeten React-Redux und DVA beide Kapselungen von Redux und werden derzeit häufig in groß angelegten Anwendungen verwendet. Es wird empfohlen, die Kernideen von Redux über die offizielle Website und den Quellcode kennenzulernen, um die Fähigkeit zum Lesen von Quellcode zu verbessern.

Oben finden Sie eine ausführliche Erläuterung des JavaScript-Statuscontainers Redux. Weitere Informationen zum JavaScript-Statuscontainer Redux finden Sie in den anderen verwandten Artikeln auf 123WORDPRESS.COM!

Das könnte Sie auch interessieren:
  • Detaillierte Erklärung der Verwendung und Prinzipanalyse von Connect in React-Redux
  • Verstehen Sie die anfängliche Verwendung von Redux in React in einem Artikel
  • Erklärung der Funktionsweise und Verwendung von Redux

<<:  Tutorial zur Installation von Pycharm und Ipython unter Ubuntu 16.04/18.04

>>:  Detaillierte Erklärung der RPM-Installation in MySQL

Artikel empfehlen

Fallstudie zum Zusammenführen von JavaScript-Arrays

Methode 1: var a = [1,2,3]; var b=[4,5] a = a.con...

So handhaben Sie lange Daten bei der Anzeige in HTML

Bei der Anzeige langer Daten in HTML können Sie di...

js implementiert das Umschalten von Bildern per Maus (ohne Timer)

In diesem Artikelbeispiel wird der spezifische Co...

So deaktivieren Sie die Eslint-Erkennung in Vue (mehrere Methoden)

Inhaltsverzeichnis 1. Problembeschreibung 2. Prob...

Teilen Sie 8 sehr nützliche CSS-Entwicklungstools

CSS3-Mustergalerie Diese CSS3-Musterbibliothek ze...

Detaillierte Erklärung, wo Docker Protokolldateien speichert

Inhaltsverzeichnis Wo werden die Protokolle gespe...

Docker installiert Redis und führt den visuellen Client für den Betrieb ein

1 Einleitung Redis ist eine leistungsstarke, auf ...

Beispielcode zur Umsetzung des „Pluszeichen“-Effektes mit CSS

So erzielen Sie den unten gezeigten Pluszeichen-E...

Details der MySQL-Berechnungsfunktion

Inhaltsverzeichnis 2. Feldverkettung 2. Geben Sie...

Vollständige Schritte zur Deinstallation der MySQL-Datenbank

Der Vorgang zur vollständigen Deinstallation der ...

Beispiel für die Erschöpfung der MySQL-Auto-Increment-ID

Anzeigedefinitions-ID Wenn die in der Tabelle def...