Hallo allerseits! Mein Name ist Arthur, ich arbeite als VKontakte im mobilen
Webteam , ich bin am
VKUI- Projekt beteiligt - einer Bibliothek von React-Komponenten, mit deren Hilfe einige unserer Schnittstellen in mobilen Anwendungen geschrieben werden. Die Frage der Arbeit mit einem globalen Staat steht uns noch offen. Es gibt mehrere bekannte Ansätze: Redux, MobX, Context API. Kürzlich bin ich auf einen Artikel von André Gardi
State Management mit React Hooks - No Redux oder Context API gestoßen , in dem der Autor vorschlägt, React Hooks zu verwenden, um den Status der Anwendung zu steuern.
Hooks brechen schnell in das Leben von Entwicklern ein und bieten neue Möglichkeiten, um verschiedene Aufgaben und Ansätze zu lösen oder zu überdenken. Sie verändern unser Verständnis nicht nur der Beschreibung von Komponenten, sondern auch des Umgangs mit Daten. Lesen Sie die Übersetzung des Artikels und den Kommentar des Übersetzers unter der Katze.

Reaktionshaken sind leistungsfähiger als Sie denken
Heute werden wir React Hooks untersuchen und einen benutzerdefinierten Hook zum Verwalten des globalen Status der Anwendung entwickeln, der einfacher als die Redux-Implementierung und produktiver als die Context-API ist.
Grundlagen von Hooks reagieren
Sie können diesen Teil überspringen, wenn Sie bereits mit Hooks vertraut sind.
useState ()
Vor dem Auftreten von Hooks konnten Funktionskomponenten keinen lokalen Status festlegen. Die Situation hat sich mit dem Aufkommen von
useState()
geändert.

Dieser Aufruf gibt ein Array zurück. Das erste Element ist eine Variable, die den Zugriff auf den Statuswert ermöglicht. Das zweite Element ist eine Funktion, die den Status aktualisiert und die Komponente neu zeichnet, um die Änderungen widerzuspiegeln.
import React, { useState } from 'react'; function Example() { const [state, setState] = useState({counter:0}); const add1ToCounter = () => { const newCounterValue = state.counter + 1; setState({ counter: newCounterValue}); } return ( <div> <p>You clicked {state.counter} times</p> <button onClick={add1ToCounter}> Click me </button> </div> ); }
useEffect ()
Klassenkomponenten reagieren auf Nebenwirkungen mithilfe von Lebenszyklusmethoden wie
componentDidMount()
. Mit dem
useEffect()
Hook können Sie dasselbe in Funktionskomponenten tun.
Standardmäßig werden Effekte nach jedem Neuzeichnen ausgelöst. Sie können jedoch sicherstellen, dass sie erst ausgeführt werden, nachdem Sie die Werte bestimmter Variablen geändert haben, und ihnen den zweiten optionalen Parameter in Form eines Arrays übergeben.
Um ein ähnliches Ergebnis wie
componentDidMount()
zu erzielen, übergeben wir ein leeres Array an den zweiten Parameter. Da der Inhalt eines leeren Arrays immer unverändert bleibt, wird der Effekt nur einmal ausgeführt.
Staatsteilung
Wir haben gesehen, dass ein Hook-Status genauso funktioniert wie ein Klassenkomponentenstatus. Jede Komponenteninstanz hat ihren eigenen internen Status.
Um den Status zwischen den Komponenten zu teilen, erstellen wir unseren eigenen Hook.

Die Idee ist, ein Array von Listenern und nur einen Status zu erstellen. Jedes Mal, wenn eine Komponente den Status ändert, rufen alle abonnierten Komponenten ihren
getState()
und werden aus diesem Grund aktualisiert.
Wir können dies erreichen, indem
useState()
in unserem benutzerdefinierten Hook aufrufen. Anstatt die Funktion
setState()
, fügen wir sie dem Array von Listenern hinzu und geben eine Funktion zurück, die das
setState()
intern aktualisiert und alle Listener aufruft.
Warte einen Moment. Wie erleichtert es mir das Leben?
Ja, Sie haben Recht. Ich habe ein
NPM-Paket erstellt , das die gesamte beschriebene Logik enthält.
Sie müssen es nicht in jedem Projekt implementieren. Wenn Sie keine Zeit mehr mit Lesen verbringen und das Endergebnis sehen möchten, fügen Sie dieses Paket einfach Ihrer Anwendung hinzu.
npm install -s use-global-hook
Um zu verstehen, wie mit einem Paket gearbeitet wird, lesen Sie die Beispiele in der Dokumentation. Und jetzt möchte ich mich darauf konzentrieren, wie das Paket im Inneren angeordnet ist.
Erste Version
import { useState, useEffect } from 'react'; let listeners = []; let state = { counter: 0 }; const setState = (newState) => { state = { ...state, ...newState }; listeners.forEach((listener) => { listener(state); }); }; const useCustom = () => { const newListener = useState()[1]; useEffect(() => { listeners.push(newListener); }, []); return [state, setState]; }; export default useCustom;
Verwendung in Komponenten
import React from 'react'; import useCustom from './customHook'; const Counter = () => { const [globalState, setGlobalState] = useCustom(); const add1Global = () => { const newCounterValue = globalState.counter + 1; setGlobalState({ counter: newCounterValue }); }; return ( <div> <p> counter: {globalState.counter} </p> <button type="button" onClick={add1Global}> +1 to global </button> </div> ); }; export default Counter;
Diese Version bietet bereits den Freigabestatus. Sie können Ihrer Anwendung eine beliebige Anzahl von Zählern hinzufügen, die alle einen gemeinsamen globalen Status haben.
Aber wir können es besser machen
Was willst du:
- Entfernen Sie den Listener aus dem Array, wenn Sie die Bereitstellung der Komponente aufheben.
- Machen Sie den Hook abstrakter, um ihn in anderen Projekten zu verwenden.
- Verwalten von
initialState
mithilfe von Parametern. - Schreiben Sie den Haken in einem funktionaleren Stil um.
Aufrufen einer Funktion kurz vor dem Aufheben der Bereitstellung einer Komponente
Wir haben bereits herausgefunden, dass das Aufrufen von
useEffect(function, [])
mit einem leeren Array genauso funktioniert wie
componentDidMount()
. Wenn die im ersten Parameter übergebene Funktion jedoch eine andere Funktion zurückgibt, wird die zweite Funktion unmittelbar vor dem Aufheben der Bereitstellung der Komponente aufgerufen. Genau wie
componentWillUnmount()
.
Im Code der zweiten Funktion können Sie also die Logik zum Entfernen einer Komponente aus einem Array von Listenern schreiben.
const useCustom = () => { const newListener = useState()[1]; useEffect(() => {
Zweite Version
Zusätzlich zu diesem Update planen wir auch:
- Übergeben Sie den React-Parameter und entfernen Sie den Import.
- exportiere nicht customHook, sondern eine Funktion, die customHook mit dem angegebenen
initalState
; - Erstellen Sie ein
store
, das den setState()
und die Funktion setState()
. - Ersetzen Sie die
setState()
durch die üblichen in setState()
und useCustom()
damit Sie den store
verknüpfen können.
function setState(newState) { this.state = { ...this.state, ...newState }; this.listeners.forEach((listener) => { listener(this.state); }); } function useCustom(React) { const newListener = React.useState()[1]; React.useEffect(() => {
Trennen Sie Aktionen von Komponenten
Wenn Sie jemals mit komplexen Statusverwaltungsbibliotheken gearbeitet haben, wissen Sie, dass die Manipulation eines globalen Status aus Komponenten keine gute Idee ist.
Es wäre richtiger, die Geschäftslogik zu trennen, indem Aktionen zum Ändern des Status erstellt werden. Daher möchte ich, dass die neueste Version des Pakets den Komponenten Zugriff nicht auf
setState()
, sondern auf eine Reihe von Aktionen
setState()
.
Dazu liefern wir unseren
useGlobalHook(React, initialState, actions)
dritten Argument. Ich möchte nur ein paar Kommentare hinzufügen.
- Aktionen haben Zugriff auf das
store
. Auf diese Weise können Aktionen den Inhalt von store.state
lesen, den store.setState()
Aufrufen von store.setState()
aktualisieren und sogar andere store.actions
durch store.actions
von store.actions
. - Um Unordnung zu vermeiden, kann das Aktionsobjekt Unterobjekte enthalten. Auf diese Weise können Sie
actions.addToCounter(amount
) mit allen Gegenaktionen an ein Unterobjekt übertragen: actions.counter.add(amount)
.
Endgültige Version
Das folgende Snippet ist die aktuelle Version des NPM-Pakets
use-global-hook
.
function setState(newState) { this.state = { ...this.state, ...newState }; this.listeners.forEach((listener) => { listener(this.state); }); } function useCustom(React) { const newListener = React.useState()[1]; React.useEffect(() => { this.listeners.push(newListener); return () => { this.listeners = this.listeners.filter(listener => listener !== newListener); }; }, []); return [this.state, this.actions]; } function associateActions(store, actions) { const associatedActions = {}; Object.keys(actions).forEach((key) => { if (typeof actions[key] === 'function') { associatedActions[key] = actions[key].bind(null, store); } if (typeof actions[key] === 'object') { associatedActions[key] = associateActions(store, actions[key]); } }); return associatedActions; } const useGlobalHook = (React, initialState, actions) => { const store = { state: initialState, listeners: [] }; store.setState = setState.bind(store); store.actions = associateActions(store, actions); return useCustom.bind(store, React); }; export default useGlobalHook;
Anwendungsbeispiele
Sie müssen sich nicht mehr mit
useGlobalHook.js
befassen. Jetzt können Sie sich auf Ihre Anwendung konzentrieren. Im Folgenden finden Sie zwei Beispiele für die Verwendung des Pakets.
Mehrere Zähler, ein Wert
Fügen Sie so viele Zähler hinzu, wie Sie möchten: Sie haben alle einen globalen Wert. Jedes Mal, wenn einer der Zähler den globalen Status erhöht, werden alle anderen neu gezeichnet. In diesem Fall muss die übergeordnete Komponente nicht neu gezeichnet werden.
Lebendes Beispiel .
Asynchrone Ajax-Anforderungen
Durchsuchen Sie GitHub-Repositorys nach Benutzernamen. Wir verarbeiten Ajax-Anfragen asynchron mit async / await. Wir aktualisieren den Abfragezähler bei jeder neuen Suche.
Lebendes Beispiel .
Nun, das ist alles
Wir haben jetzt unsere eigene State-Management-Bibliothek für React Hooks.
Übersetzerkommentar
Die meisten vorhandenen Lösungen sind im Wesentlichen separate Bibliotheken. In diesem Sinne ist der vom Autor beschriebene Ansatz insofern interessant, als er nur die integrierten React-Funktionen verwendet. Darüber hinaus reduziert dieser Ansatz im Vergleich zu derselben Context-API, die ebenfalls sofort verfügbar ist, die Anzahl unnötiger Neuzeichnungen und gewinnt somit an Leistung.