
In React 16.3 wurde eine neue Kontext-API hinzugefügt.
Neu in dem Sinne, dass sich die
alte Kontext-API hinter den Kulissen befand, wussten die meisten Menschen entweder nichts über ihre Existenz oder verwendeten sie nicht, da in der Dokumentation empfohlen wurde, sie nicht zu verwenden.
Jetzt ist die Kontext-API jedoch ein vollwertiger Teil von React, der zur Verwendung geöffnet ist (offiziell nicht wie zuvor).
Unmittelbar nach der Veröffentlichung von React 16.3 erschienen Artikel, die den Tod von Redux aufgrund der neuen Kontext-API proklamierten. Wenn Sie Redux danach gefragt hätten, hätte er wahrscheinlich geantwortet: "Die Berichte über meinen Tod sind
stark übertrieben ."
In diesem Beitrag möchte ich darüber sprechen, wie die neue Kontext-API funktioniert, wie sie wie Redux aussieht, wann Sie Context anstelle von Redux verwenden können und warum Context Redux nicht in jedem Fall ersetzt.
Wenn Sie nur einen Überblick über die Kontext-API wünschen, können Sie dem Link folgen .Anwendungsbeispiel reagieren
Ich werde vorschlagen, dass Sie die Prinzipien der Arbeit mit State in React (Requisiten & State) verstehen, aber wenn dies nicht der Fall ist, habe ich einen kostenlosen 5-tägigen Kurs, der
Ihnen dabei hilft, mehr darüber zu erfahren .
Schauen wir uns ein Beispiel an, das uns zu dem in Redux verwendeten Konzept bringt. Wir werden mit einer einfachen Version von React beginnen und dann sehen, wie es in Redux und schließlich mit Context aussieht.

In dieser Anwendung werden Benutzerinformationen an zwei Stellen angezeigt: in der Navigationsleiste in der oberen rechten Ecke und im Seitenbereich neben dem Hauptinhalt.
(Möglicherweise stellen Sie fest, dass es viele Ähnlichkeiten mit Twitter gibt. Dies ist kein Zufall. Eine der besten Möglichkeiten, Ihre Reaktionsfähigkeiten zu verbessern, ist das
Kopieren (Erstellen von Replikaten vorhandener Websites / Anwendungen) .
Die Struktur der Komponente sieht folgendermaßen aus:

Bei Verwendung von pure React (nur Requisiten) müssen Benutzerinformationen hoch genug im Baum gespeichert werden, damit sie an die Komponenten übergeben werden können, die sie benötigen. In diesem Fall müssen sich die Benutzerinformationen in der App befinden.
Um Informationen über den Benutzer an die Komponenten zu übertragen, die sie benötigen, muss die Anwendung sie an Nav und Body übergeben. Sie werden es wiederum an UserAvatar (Hurra!) Und Sidebar weitergeben. Schließlich sollte Sidebar es an UserStats übergeben.
Mal sehen, wie das im Code funktioniert (ich habe alles in eine Datei eingefügt, um das Lesen zu erleichtern, aber tatsächlich wird es wahrscheinlich nach einer
Standardstruktur in separate Dateien aufgeteilt).
import React from "react"; import ReactDOM from "react-dom"; import "./styles.css"; const UserAvatar = ({ user, size }) => ( <img className={`user-avatar ${size || ""}`} alt="user avatar" src={user.avatar} /> ); const UserStats = ({ user }) => ( <div className="user-stats"> <div> <UserAvatar user={user} /> {user.name} </div> <div className="stats"> <div>{user.followers} Followers</div> <div>Following {user.following}</div> </div> </div> ); const Nav = ({ user }) => ( <div className="nav"> <UserAvatar user={user} size="small" /> </div> ); const Content = () => <div className="content">main content here</div>; const Sidebar = ({ user }) => ( <div className="sidebar"> <UserStats user={user} /> </div> ); const Body = ({ user }) => ( <div className="body"> <Sidebar user={user} /> <Content user={user} /> </div> ); class App extends React.Component { state = { user: { avatar: "https://www.gravatar.com/avatar/5c3dd2d257ff0e14dbd2583485dbd44b", name: "Dave", followers: 1234, following: 123 } }; render() { const { user } = this.state; return ( <div className="app"> <Nav user={user} /> <Body user={user} /> </div> ); } } ReactDOM.render(<App />, document.querySelector("#root"));
CodeSandbox-BeispielcodeHier
initialisiert App
den Status , der das Benutzerobjekt enthält. In einer realen Anwendung
extrahieren Sie diese Daten höchstwahrscheinlich
vom Server und speichern sie im Status zum Rendern.
In Bezug auf Requisiten ("Requisitenbohren") ist
dies keine große Sache . Es funktioniert großartig. Das Werfen von Requisiten ist ein ideales Beispiel für React. Aber tief in den Staatsbaum zu werfen kann beim Schreiben etwas nervig sein. Und umso ärgerlicher, wenn Sie eine Menge Requisiten (und nicht eine) übertragen müssen.
Diese Strategie hat jedoch ein großes Minus: Sie stellt eine Verbindung zwischen Komponenten her, die nicht verbunden werden sollten. Im obigen Beispiel sollte Nav eine "Benutzer" -Stütze akzeptieren und an UserAvatar übergeben, auch wenn Nav sie nicht benötigt.
Eng gekoppelte Komponenten (z. B. solche, die Requisiten an ihre Kinder weitergeben) sind schwerer wiederzuverwenden, da Sie sie an neue Eltern binden müssen, wenn Sie sie an einem neuen Ort verwenden.
Mal sehen, wie wir das verbessern können.
Vor der Verwendung von Context oder Redux ...
Wenn Sie eine Möglichkeit finden
, die Struktur Ihrer Anwendung zu
kombinieren und Requisiten an Nachkommen weiterzugeben, kann dies Ihren Code sauberer machen, ohne auf Requisiten,
Kontext oder
Redux zurückgreifen zu müssen.
In diesem Beispiel sind Kinderrequisiten eine großartige Lösung für Komponenten, die universell sein müssen, wie z. B. Nav, Sidebar und Body. Beachten Sie auch, dass Sie JSX an
jede Requisite weitergeben können, nicht nur an Kinder. Wenn Sie daher mehr als einen „Steckplatz“ zum Verbinden von Komponenten benötigen, denken Sie daran.
Hier ist ein Beispiel für eine React-Anwendung, in der Nav, Body und Sidebar Kinder aufnehmen und so anzeigen, wie sie sind. Somit muss sich derjenige, der die Komponente verwendet, nicht um die Übertragung bestimmter Daten kümmern, die die Komponente benötigt. Er kann einfach anzeigen, was er vor Ort benötigt, und dabei die Daten verwenden, über die er bereits verfügt. Dieses Beispiel zeigt auch, wie Sie mit einer Requisite Kinder übertragen können.
(Danke an Dan Abramov für
dieses Angebot !)
import React from "react"; import ReactDOM from "react-dom"; import "./styles.css"; const UserAvatar = ({ user, size }) => ( <img className={`user-avatar ${size || ""}`} alt="user avatar" src={user.avatar} /> ); const UserStats = ({ user }) => ( <div className="user-stats"> <div> <UserAvatar user={user} /> {user.name} </div> <div className="stats"> <div>{user.followers} Followers</div> <div>Following {user.following}</div> </div> </div> ); // children . const Nav = ({ children }) => ( <div className="nav"> {children} </div> ); const Content = () => ( <div className="content">main content here</div> ); const Sidebar = ({ children }) => ( <div className="sidebar"> {children} </div> ); // Body sidebar content, , // . const Body = ({ sidebar, content }) => ( <div className="body"> <Sidebar>{sidebar}</Sidebar> {content} </div> ); class App extends React.Component { state = { user: { avatar: "https://www.gravatar.com/avatar/5c3dd2d257ff0e14dbd2583485dbd44b", name: "Dave", followers: 1234, following: 123 } }; render() { const { user } = this.state; return ( <div className="app"> <Nav> <UserAvatar user={user} size="small" /> </Nav> <Body sidebar={<UserStats user={user} />} content={<Content />} /> </div> ); } } ReactDOM.render(<App />, document.querySelector("#root"));
CodeSandbox-BeispielcodeWenn Ihre Anwendung zu kompliziert ist (komplizierter als dieses Beispiel!), Ist es möglicherweise schwierig zu verstehen, wie die Vorlage an Kinder angepasst wird. Mal sehen, wie Sie die Requisitenweiterleitung durch Redux ersetzen können.
Redux Beispiel
Ich werde einen kurzen Blick auf das Redux-Beispiel werfen, damit wir besser verstehen, wie der Kontext funktioniert. Wenn Sie also kein klares Verständnis von Redux haben, lesen Sie zuerst
meine Einführung in Redux (oder sehen Sie sich das
Video an ).
Hier ist unsere React-App, die für die Verwendung von Redux neu gestaltet wurde. Benutzerinformationen wurden in den Redux-Speicher verschoben. Dies bedeutet, dass wir die React-Redux-Verbindungsfunktion verwenden können, um die Benutzer-Requisite direkt an die Komponenten zu übergeben, die sie benötigen.
Dies ist ein großer Sieg in Bezug auf die Beseitigung der Verbundenheit. Wenn Sie sich Nav, Body und Sidebar ansehen, werden Sie feststellen, dass sie keine Benutzer-Requisiten mehr empfangen oder senden. Sie spielen keine heißen Kartoffeln mehr mit Requisiten. Keine nutzlosen Verbindungen mehr.
Der Reduzierer macht hier wenig; es ist ziemlich einfach. Ich habe noch eine Sache darüber, wie
Redux-Reduzierer funktionieren und
wie man den unveränderlichen Code schreibt, den sie verwenden.
import React from "react"; import ReactDOM from "react-dom";
CodeSandbox-BeispielcodeJetzt fragen Sie sich wahrscheinlich, wie Redux diese Magie erreicht. Erstaunlich Wie unterstützt React das Weitergeben von Requisiten an mehrere Ebenen nicht und kann Redux dies tun?
Die Antwort ist, dass Redux die
Kontextfunktion React (Kontextfunktion) verwendet. Nicht die moderne Kontext-API (noch nicht), sondern die alte. Eine, die laut React-Dokumentation nur verwendet wird, wenn Sie Ihre eigene Bibliothek schreiben oder wissen, was Sie tun.
Der Kontext ähnelt einem Computerbus, der jeder Komponente folgt: Um die Stromversorgung (Daten) durchzulassen, müssen Sie nur eine Verbindung herstellen. Und React-Redux Connect macht genau das.
Diese Redux-Funktion ist jedoch nur die Spitze des Eisbergs. Das Übertragen von Daten an den richtigen Ort ist die
offensichtlichste Funktion von Redux. Hier sind einige weitere Vorteile, die Sie sofort nutzen können:
verbinden ist eine reine FunktionConnect macht verbundene Komponenten automatisch „sauber“, dh sie werden nur neu gerendert, wenn sich ihre Requisiten ändern - dh wenn sich ihr Teil des Redux-Status ändert. Dies verhindert unnötiges erneutes Rendern und beschleunigt die Anwendung.
Einfaches Debuggen mit ReduxDie Zeremonie des Schreibens von Aktionen und Reduzierungen wird durch die erstaunliche Leichtigkeit des Debuggens ausgeglichen, die Redux Ihnen bietet.
Mit
der Redux DevTools-Erweiterung erhalten Sie ein automatisches Protokoll aller von Ihrer Anwendung ausgeführten Aktionen. Sie können es jederzeit öffnen und sehen, welche Aktionen gestartet wurden, wie hoch ihre Nutzlast ist und welchen Status sie vor und nach der Aktion haben.

Eine weitere großartige Funktion, die Redux DevTools bietet, ist das Debuggen mit
"Zeitreise" . Das heißt, Sie können auf eine beliebige vorherige Aktion klicken und bis zu der aktuellen Aktion zu diesem Zeitpunkt wechseln. Der Grund dafür ist, dass jede Aktion den Speicher auf die
gleiche Weise aktualisiert, sodass Sie eine Liste der aufgezeichneten Statusaktualisierungen erstellen und ohne Nebenwirkungen abspielen und an der gewünschten Stelle landen können.
Es gibt auch Tools wie
LogRocket , mit denen Sie für jeden Ihrer Benutzer die permanenten Redux DevTools in der
Produktion erhalten . Hast du einen Fehlerbericht? Kein Problem. Wenn Sie sich diese Benutzersitzung in LogRocket ansehen, sehen Sie eine Wiederholung dessen, was er getan hat und welche Aktionen gestartet wurden. All dies funktioniert mit dem Redux-Aktionsstrom.
Redux mit Middleware erweiternRedux unterstützt das Middleware-Konzept (ein ausgefallenes Wort für „eine Funktion, die jedes Mal ausgeführt wird, wenn eine Aktion gesendet wird“). Das Schreiben Ihrer eigenen Middleware ist nicht so schwierig, wie es scheint, und ermöglicht es Ihnen, einige leistungsstarke Tools zu verwenden.
Zum Beispiel ...
- Möchten Sie jedes Mal eine API-Anfrage senden, wenn ein Aktionsname mit FETCH_ beginnt? Sie können dies mit Middleware tun.
- Möchten Sie einen zentralen Ort zum Protokollieren von Ereignissen in Ihrer Analysesoftware? Middleware ist ein guter Ort, um dies zu tun.
- Möchten Sie verhindern, dass eine Aktion zu einem bestimmten Zeitpunkt gestartet wird? Sie können dies mit Middleware tun, die für den Rest Ihrer Anwendung unsichtbar ist.
- Möchten Sie eine Aktion mit einem JWT-Token abfangen und automatisch in localStorage speichern? Ja, Middleware.
Hier ist ein guter
Artikel mit Beispielen zum Schreiben von Redux-Middleware.
Verwendung der React Context API
Aber vielleicht brauchen Sie nicht alle diese Redux-Kuriositäten. Möglicherweise müssen Sie nicht einfach debuggen, optimieren oder die Leistung automatisch verbessern. Sie möchten lediglich Daten einfach übertragen. Vielleicht ist Ihre Anwendung klein, oder Sie müssen nur schnell etwas unternehmen und sich später mit den Feinheiten befassen.
Die neue Kontext-API ist wahrscheinlich das Richtige für Sie. Mal sehen, wie es funktioniert.
Ich habe ein kurzes Context API-
Tutorial auf Egghead veröffentlicht, wenn Sie lieber zuschauen als lesen möchten (3:43).
Hier sind 3 wichtige Komponenten der Kontext-API:
- React.createContext-Funktion, die den Kontext erstellt
- Provider (gibt createContext zurück), der den "Bus" einrichtet,
Durchlaufen des Komponentenbaums - Consumer (auch createContext zurückgegeben), der in sich eintaucht
"Elektrobus" zur Datenextraktion
Provider ist Provider in React-Redux sehr ähnlich. Es nimmt einen Wert an, der alles sein kann, was Sie wollen (es könnte sogar ein Redux-Laden sein ... aber das wäre dumm). Dies ist höchstwahrscheinlich ein Objekt, das Ihre Daten und alle Aktionen enthält, die Sie mit den Daten ausführen möchten.
Consumer funktioniert ein bisschen wie die Verbindungsfunktion in React-Redux, stellt eine Verbindung zu Daten her und stellt sie einer Komponente zur Verfügung, die sie verwendet.
Hier sind die Highlights:
CodeSandbox-BeispielcodeSchauen wir uns an, wie es funktioniert.
Denken Sie daran, wir haben drei Teile: den Kontext selbst (erstellt mit React.createContext) und zwei Komponenten, die mit ihm interagieren (Provider und Consumer).
Anbieter und Verbraucher arbeiten zusammenAnbieter und Verbraucher sind miteinander verbunden und untrennbar miteinander verbunden. Sie wissen nur, wie sie miteinander umgehen sollen. Wenn Sie zwei separate Kontexte erstellt haben, z. B. "Kontext1" und "Kontext2", können Anbieter- und Verbraucherkontext1 nicht mit Anbieter- und Verbraucherkontext2 kommunizieren.
Der Kontext enthält keinen StatusBeachten Sie, dass der Kontext
keinen eigenen Status hat . Dies ist nur ein Kanal für Ihre Daten. Sie müssen den Wert an den Anbieter übergeben, und dieser Wert wird an jeden Verbraucher übergeben, der weiß, wie er danach sucht (der Anbieter ist an denselben Kontext wie der Verbraucher gebunden).
Wenn Sie einen Kontext erstellen, können Sie den "Standard" wie folgt übergeben:
const Ctx = React.createContext(yourDefaultValue);
Der Standardwert ist der Wert, den der Verbraucher erhält, wenn er in den Baum gestellt wird, ohne dass der Anbieter darüber steht. Wenn Sie es nicht übergeben, ist der Wert undefiniert. Beachten Sie, dass dies
der Standardwert ist , nicht der
Anfangswert . Der Kontext speichert nichts; Es verbreitet nur die Daten, die Sie übergeben.
Verbraucher verwendet Render RequisitenmusterDie Connect Redux-Funktion ist eine Komponente höherer Ordnung (abgekürzt als HoC). Es verpackt eine andere Komponente und gibt Requisiten hinein.
Im Gegensatz dazu erwartet der Verbraucher, dass die untergeordnete Komponente eine Funktion ist. Anschließend ruft es diese Funktion während des Renderns auf und übergibt den vom Provider empfangenen Wert irgendwo darüber (entweder den Standardwert aus dem Kontext oder undefiniert, wenn Sie den Standardwert nicht übergeben haben).
Der Anbieter nimmt einen Wert an.Nur ein Wert, wie Stütze. Aber denken Sie daran, dass der Wert alles sein kann. In der Praxis müssen Sie, wenn Sie mehrere Werte weitergeben möchten, ein Objekt mit allen Werten erstellen und dieses Objekt weitergeben.
Kontext-API flexibel
Da wir durch das Erstellen eines Kontexts zwei Komponenten erhalten, mit denen wir arbeiten können (Anbieter und Verbraucher), können wir sie nach Belieben verwenden. Hier sind einige Ideen.
Wrap Consumer in HOCGefällt Ihnen die Idee nicht, UserContext.Consumer an jedem Ort hinzuzufügen, der sie benötigt? Das ist dein Code! Sie haben das Recht zu entscheiden, welche Ihre beste Wahl ist.
Wenn Sie den Wert lieber als Requisite erhalten möchten, können Sie einen kleinen Wrapper um Consumer wie folgt schreiben:
function withUser(Component) { return function ConnectedComponent(props) { return ( <UserContext.Consumer> {user => <Component {...props} user={user}/>} </UserContext.Consumer> ); } }
Danach können Sie beispielsweise UserAvatar mit der Funktion withUser neu schreiben:
const UserAvatar = withUser(({ size, user }) => ( <img className={`user-avatar ${size || ""}`} alt="user avatar" src={user.avatar} /> ));
Und voila, der Kontext kann genauso funktionieren wie Connectx verbinden. Minus automatische Sauberkeit.
Hier ist ein Beispiel für
CodeSandbox mit diesem HOC.
Behalten Sie den Status im AnbieterDenken Sie daran, dass der Anbieter nur ein Kanal ist. Es werden keine Daten gespeichert. Dies hindert Sie jedoch nicht daran, einen
eigenen Wrapper zum Speichern von Daten zu erstellen.
Im obigen Beispiel werden die Daten in der App gespeichert. Das einzige, was Sie verstehen mussten, waren die Provider + Consumer-Komponenten. Aber vielleicht möchten Sie Ihr eigenes Geschäft erstellen. Sie können eine Komponente erstellen, um den Status zu speichern und sie durch den Kontext zu leiten:
class UserStore extends React.Component { state = { user: { avatar: "https://www.gravatar.com/avatar/5c3dd2d257ff0e14dbd2583485dbd44b", name: "Dave", followers: 1234, following: 123 } }; render() { return ( <UserContext.Provider value={this.state.user}> {this.props.children} </UserContext.Provider> ); } } // ... ... const App = () => ( <div className="app"> <Nav /> <Body /> </div> ); ReactDOM.render( <UserStore> <App /> </UserStore>, document.querySelector("#root") );
Jetzt sind Benutzerdaten in einer
eigenen Komponente enthalten, deren einzige Aufgabe diese Daten sind. Cool. App kann wieder zustandslos werden. Ich denke, es sieht ein bisschen sauberer aus.
Hier ist ein
CodeSandbox- Beispiel mit diesem UserStore.
Aktionen durch den Kontext werfenDenken Sie daran, dass das über den Provider übergebene Objekt alles enthalten kann, was Sie möchten. Dies bedeutet, dass es Funktionen enthalten kann. Sie können ihnen sogar Aktionen nennen.
Hier ist ein neues Beispiel: Ein einfacher Raum mit einem Schalter zum Umschalten der Hintergrundfarbe - oh, ich meine Licht.

Der Status wird im Speicher gespeichert, der auch eine Lichtschaltfunktion hat. Sowohl Zustand als auch Funktion werden durch den Kontext geleitet.
import React from "react"; import ReactDOM from "react-dom"; import "./styles.css";
Hier ist ein vollständiges Arbeitsbeispiel in
CodeSandbox .
Also, was soll man verwenden, Context oder Redux?Nachdem Sie beide Pfade gesehen haben, welchen ist es wert, benutzt zu werden? Ich weiß, dass Sie nur die Antwort auf diese Frage hören möchten, aber ich muss antworten: "Es hängt von Ihnen ab."
Dies hängt davon ab, wie groß Ihre Anwendung jetzt ist oder wie schnell sie wächst. Wie viele Leute werden daran arbeiten - nur Sie oder ein großes Team? Wie erfahren sind Sie oder Ihr Team in der Arbeit mit den Funktionskonzepten, auf die sich Redux stützt (wie Unveränderlichkeit und reine Funktionen).
Ein fataler Fehler, der das gesamte JavaScript-Ökosystem durchdringt, ist die Idee des
Wettbewerbs . Es gibt die Idee, dass jede Auswahl ein Nullsummenspiel ist: Wenn Sie
Bibliothek A verwenden, sollten Sie nicht die Konkurrenzbibliothek B verwenden. Wenn eine neue Bibliothek herauskommt, die besser als die vorherige ist, sollte sie die vorhandene verdrängen. Dass alles entweder / oder sein sollte, dass Sie entweder das Neueste und das Beste auswählen oder mit Entwicklern aus der Vergangenheit in den Hintergrund treten müssen.
Der beste Ansatz ist es, diese wunderbare Wahl anhand eines Beispiels, einer Reihe von Werkzeugen, zu betrachten. Es ist wie die Wahl zwischen einem Schraubendreher oder einem leistungsstarken Schraubendreher. In 80% der Fälle erledigt ein Schraubendreher die Arbeit einfacher und schneller als ein Schraubendreher. Für die anderen 20% wäre ein Schraubendreher die beste Wahl (nicht genügend Platz oder der Gegenstand ist dünn). Als ich einen Schraubenzieher kaufte, warf ich den Schraubenzieher nicht sofort weg, er ersetzte ihn nicht, sondern gab mir einfach eine andere Option. Ein anderer Weg, um das Problem zu lösen.
Der Kontext "ersetzt" Redux nicht, nicht mehr als "React" ersetzt "Angular oder jQuery. Zur Hölle, ich benutze immer noch jQuery, wenn ich schnell etwas tun muss. Manchmal verwende ich immer noch serverseitige EJS-Vorlagen, anstatt eine React-Anwendung bereitzustellen. Manchmal ist Reagieren mehr als Sie benötigen, um eine Aufgabe zu erledigen. Gleiches gilt für Redux.
Wenn Redux heute mehr ist als Sie benötigen, können Sie den Kontext verwenden.
Lernreaktionen können schwierig sein - es gibt so viele Bibliotheken und Tools!Mein Rat Ignoriere sie alle :)Für eine Schritt-für-Schritt-Anleitung lies mein Buch Pure React .