Context ist derzeit eine experimentelle API für React – aber bald ein vollwertiges Element! Es gibt viele Gründe, warum es interessant ist, aber vielleicht am wichtigsten ist, dass es übergeordneten Komponenten ermöglicht, Daten implizit an ihre Kindkomponenten weiterzugeben, unabhängig davon, wie tief der Komponentenbaum ist. Mit anderen Worten, Daten können einer übergeordneten Komponente hinzugefügt werden, und dann kann jede Kindkomponente darauf zugreifen.
See the Pen React Context Lights by Neal Fennimore (@nealfennimore) on CodePen.
Dies ist zwar oft der Anwendungsfall für die Verwendung von etwas wie Redux, aber es ist gut, wenn Sie keine komplexe Datenverwaltung benötigen. Denken Sie darüber nach! Wir erstellen einen benutzerdefinierten Datenstrom und entscheiden, welche Props auf welchen Ebenen weitergegeben werden. Ziemlich cool.
Context ist großartig für Bereiche, in denen viele Komponenten von einer einzelnen Datenquelle abhängen, sich aber tief im Komponentenbaum befinden. Das explizite Weitergeben jeder Prop an jede einzelne Komponente kann oft überwältigend sein, und es ist hier einfach viel einfacher, Context zu verwenden.
Betrachten wir zum Beispiel, wie wir Props normalerweise den Baum hinunter weitergeben würden. In diesem Fall übergeben wir die Farbe red mit Props an jede Komponente, um sie weiter im Stream zu bewegen.
class Parent extends React.Component {
render(){
return <Child color="red" />;
}
}
class Child extends React.Component {
render(){
return <GrandChild color={this.props.color} />
}
}
class GrandChild extends React.Component {
render(){
return (
<div style={{color: this.props.color}}>
Yep, I'm the GrandChild
</div>
);
}
}
Was wäre, wenn wir niemals wollten, dass die Child-Komponente die Prop überhaupt hat? Context erspart uns, dass wir die Child-Komponente mit der Farbe durchlaufen müssen und sie direkt von der Parent an die GrandChild weitergeben.
class Parent extends React.Component {
// Allow children to use context
getChildContext() {
return {
color: 'red'
};
}
render(){
return <Child />;
}
}
Parent.childContextTypes = {
color: PropTypes.string
};
class Child extends React.Component {
render() {
// Props is removed and context flows through to GrandChild
return <GrandChild />
}
}
class GrandChild extends React.Component {
render() {
return (
<div style={{color: this.context.color}}>
Yep, I'm still the GrandChild
</div>
);
}
}
// Expose color to the GrandChild
GrandChild.contextTypes = {
color: PropTypes.string
};
Obwohl etwas ausführlicher, ist der Vorteil, die color irgendwo im Komponentenbaum verfügbar zu machen. Nun, manchmal…
Es gibt einige Haken
Man kann nicht alles haben und das Glück genießen, und Context in seiner aktuellen Form bildet da keine Ausnahme. Es gibt einige zugrunde liegende Probleme, auf die Sie höchstwahrscheinlich stoßen werden, wenn Sie Context für alles außer den einfachsten Fällen verwenden.
Context ist großartig für die Verwendung bei einem initialen Rendern. Context unterwegs aktualisieren? Nicht so sehr. Ein häufiges Problem mit Context ist, dass Änderungen an Context **nicht** immer in einer Komponente reflektiert werden.
Lassen Sie uns diese Haken genauer untersuchen.
Haken 1: Verwendung von Pure Components
Context ist schwierig bei der Verwendung von PureComponent, da standardmäßig keine flache Differenzierung mit Context durchgeführt wird. Flache Differenzierung mit PureComponent prüft, ob die Werte des Objekts strikt gleich sind. Wenn nicht, dann (und nur dann) wird die Komponente aktualisiert. Da aber Context nicht geprüft wird, passiert nichts.
See the Pen React Context Lights with PureComponents by Neal Fennimore (@nealfennimore) on CodePen.
Haken 2: Should Component Update? Vielleicht.
Context wird auch nicht aktualisiert, wenn die shouldComponentUpdate einer Komponente false zurückgibt. Wenn Sie eine benutzerdefinierte shouldComponentUpdate-Methode haben, müssen Sie auch Context berücksichtigen. Um Updates mit Context zu ermöglichen, könnten wir jede einzelne Komponente mit einer benutzerdefinierten shouldComponentUpdate aktualisieren, die ungefähr so aussieht.
import shallowEqual from 'fbjs/lib/shallowEqual';
class ComponentThatNeedsColorContext extends React.PureComponent {
// nextContext will show color as soon as we apply ComponentThatNeedsColorContext.contextTypes
// NOTE: Doing the below will show a console error come react v16.1.1
shouldComponentUpdate(nextProps, nextState, nextContext){
return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState) || !shallowEqual(this.context, nextContext);
}
}
ComponentThatNeedsColorContext.contextTypes = {
color: PropTypes.string
};
Dies löst jedoch nicht das Problem, dass eine zwischengeschaltete PureComponent zwischen der übergeordneten und der untergeordneten Komponente Context-Updates blockiert. Das bedeutet, dass jede PureComponent zwischen der übergeordneten und der untergeordneten Komponente contextTypes darauf definiert haben müsste, und sie müssten auch eine aktualisierte shouldComponentUpdate-Methode haben. Und zu diesem Zeitpunkt ist das viel Arbeit für sehr wenig Gewinn.
Bessere Ansätze für die Haken
Glücklicherweise haben wir einige Möglichkeiten, die Haken zu umgehen.
Ansatz 1: Verwenden Sie eine Higher Order Component
Eine Higher Order Component kann aus dem Context lesen und die benötigten Werte als Prop an die nächste Komponente weitergeben.
import React from 'react';
const withColor = (WrappedComponent) => {
class ColorHOC extends React.Component {
render() {
const { color } = this.context;
return <WrappedComponent style={{color: color}} {...this.props} />
}
}
ColorHOC.contextTypes = {
color: React.PropTypes.string
};
return ColorHOC;
};
export const Button = (props)=> <button {...props}>Button</button>
// ColoredButton will render with whatever color is currently in context with a style prop
export const ColoredButton = withColor( Button );
See the Pen React Context Lights with HOC by Neal Fennimore (@nealfennimore) on CodePen.
Ansatz 2: Verwenden Sie Render Props
Render Props ermöglichen es uns, Props zu verwenden, um Code zwischen zwei Komponenten zu teilen.
class App extends React.Component {
getChildContext() {
return {
color: 'red'
}
}
render() {
return <Button />
}
}
App.childContextTypes = {
color: React.PropTypes.string
}
// Hook 'Color' into 'App' context
class Color extends React.Component {
render() {
return this.props.render(this.context.color);
}
}
Color.contextTypes = {
color: React.PropTypes.string
}
class Button extends React.Component {
render() {
return (
<button type="button">
{/* Return colored text within Button */}
<Color render={ color => (
<Text color={color} text="Button Text" />
) } />
</button>
)
}
}
class Text extends React.Component {
render(){
return (
<span style={{color: this.props.color}}>
{this.props.text}
</span>
)
}
}
Text.propTypes = {
text: React.PropTypes.string,
color: React.PropTypes.string,
}
Ansatz 3: Dependency Injection
Eine dritte Möglichkeit, diese Haken zu umgehen, ist die Verwendung von Dependency Injection, um die Context-API einzuschränken und Komponenten zu ermöglichen, sich nach Bedarf zu abonnieren.
Der neue Context
Die neue Art der Verwendung von Context, die derzeit für die nächste kleine Veröffentlichung von React (16.3) geplant ist, bietet die Vorteile, lesbarer und einfacher zu schreiben zu sein, ohne die "Haken" früherer Versionen. Wir haben jetzt eine neue Methode namens createContext, die einen neuen Context definiert und sowohl einen Provider als auch einen Consumer zurückgibt.
Der Provider stellt einen Context her, in den sich alle untergeordneten Komponenten einklinken können. Er wird über den Consumer eingehängt, der eine Render-Prop verwendet. Das erste Argument dieser Render-Prop-Funktion ist der value, den wir dem Provider gegeben haben. Durch die Aktualisierung des Werts innerhalb des Provider werden alle Consumer aktualisiert, um den neuen Wert widerzuspiegeln.
Als Nebeneffekt bei der Verwendung des neuen Context müssen wir nicht mehr childContextTypes, getChildContext und contextTypes verwenden.
const ColorContext = React.createContext('color');
class ColorProvider extends React.Component {
render(){
return (
<ColorContext.Provider value={'red'}>
{ this.props.children }
</ColorContext.Provider>
)
}
}
class Parent extends React.Component {
render(){
// Wrap 'Child' with our color provider
return (
<ColorProvider>
<Child />
</ColorProvider>
);
}
}
class Child extends React.Component {
render(){
return <GrandChild />
}
}
class GrandChild extends React.Component {
render(){
// Consume our context and pass the color into the style attribute
return (
<ColorContext.Consumer>
{/* 'color' is the value from our Provider */}
{
color => (
<div style={{color: color}}>
Yep, I'm still the GrandChild
</div>
)
}
</ColorContext.Consumer>
);
}
}
Separate Kontexte
Da wir eine granularere Kontrolle darüber haben, wie wir Context exponieren und welche Komponenten ihn verwenden dürfen, können wir Komponenten individuell mit unterschiedlichen Kontexten umschließen, auch wenn sie sich innerhalb derselben Komponente befinden. Dies sehen wir im nächsten Beispiel, bei dem wir durch die doppelte Verwendung von LightProvider zwei Komponenten einen separaten Context geben können.
See the Pen React Context Lights with new Context by Neal Fennimore (@nealfennimore) on CodePen.
Fazit
Context ist eine mächtige API, aber es ist auch sehr einfach, sie falsch zu verwenden. Es gibt auch einige Vorbehalte bei der Verwendung, und es kann sehr schwierig sein, Probleme zu identifizieren, wenn Komponenten fehlschlagen. Während Higher-Order Components und Dependency Injection Alternativen für die meisten Fälle bieten, kann Context vorteilhaft in isolierten Teilen Ihrer Codebasis verwendet werden.
Mit dem nächsten Context müssen wir uns jedoch keine Sorgen mehr über die Haken machen, die wir mit der vorherigen Version hatten. Er erspart uns die Definition von contextTypes auf einzelnen Komponenten und eröffnet das Potenzial, neue Kontexte wiederverwendbar zu definieren.
Löst dies also 1) ein Problem, das bereits von Flux/Redux gelöst wurde, 2) erhöht es die Anzahl der Zeilen Code, die für dasselbe benötigt werden, 3) erhöht es die Anzahl der Dinge, die ich als Entwickler lernen muss, 4) kommt es immer noch mit einer Reihe von Haken. Warum tun wir uns Entwickler das an?
1) Das macht keinen Sinn. Die meisten Flux/Redux-Implementierungen verwenden intern React Context. React Context erhält eine neue API, die die Haken der alten API beseitigt, sodass theoretisch auch Flux/Redux-Implementierungen davon profitieren können. Darüber hinaus, wenn Sie Flux/Redux nur verwenden, um das Weitergeben von Props zu vermeiden, verwenden Sie sie falsch. Sie sollten einfach direkt Context verwenden.
2) In vielen Fällen reduziert es die benötigte Zeilenanzahl. Aber anyway, die erhöhte Klarheit/Explizitheit der neuen API sollte eine erhöhte Zeilenanzahl wert sein.
3) Oh mein. Vergessen Sie einfach die alte API und verwenden Sie den freigewordenen Gehirnplatz für die neue? ;)
4) Haben Sie den Artikel tatsächlich zu Ende gelesen?
@saynothingetal Ich bin sicher, das ist offensichtlich, aber der Punkt von 'context' ist, dass die VIEW von React so nativ wie möglich bleibt. Während wir alle das Gamut der Flux- und Redux-Muster durchlaufen mussten, waren sie oft "unaufdringlich" (sie waren nicht für React "gebaut"). Das kann umständlich sein, wenn man versucht, die ReactJS-Bibliothek zu installieren und mit seiner Ansicht "loszulegen". Obwohl Sie durch das Erlernen der neuen "Context"-API entmutigt sein können, würde ich vorschlagen, dabei zu bleiben, da es nicht viele Male gibt, in denen Sie Redux/Flux-Bibliotheken implementieren müssen, und diese API "sollte" die Grenze verwischen, ob Sie es tun/nicht tun.
Tolle Artikel, ich möchte nur vorschlagen, vielleicht Beispiele dafür zu erstellen, wie man seinen Consumer exportiert (ich bezweifle, dass jeder eine EINE Datei schreibt, wenn er React verwendet, und ich weiß, dass es einige Versuche und Irrtümer brauchte, damit React.Consumer meinen State weitergibt).
Ja, definitiv! Das ist ein großartiger Vorschlag. Die Schönheit der neuen Context-API ist, dass sie sehr wiederverwendbar ist, sodass wir sowohl den Provider als auch den Consumer in einer einzigen Datei speichern und sie überall dort verwenden können, wo wir sie brauchen.
Es tut mir leid, aber ich verstehe nicht, warum die neue Context-API eine großartige Nachricht sein kann?
Bricht sie nicht die Datenfluss-Transparenz?
Woher weiß ich, woher die Daten kommen? Nur mit Strg+F nach demselben Context Provider-Namen?
Es erinnert mich an ein GOTO für Daten.
Wenn es schwierig wird, Daten durch viele Komponenten zu leiten, warum nicht stattdessen Komponenten weitergeben?
https://medium.com/@RubenOostinga/avoiding-deeply-nested-component-trees-973edb632991
Danke
Das sind einige interessante Fragen, Step. Mal sehen, ob wir sie ein wenig aufschlüsseln können.
1) Warum ist die neue Context-API eine großartige Neuigkeit?
Für mich ist es, weil die neue API so viel mehr bietet als die alte API. Kein Verdefinieren einer getChildContext-Methode auf einer übergeordneten Komponente mehr. Kein Definieren einer Reihe von Context-bezogenen Prop-Typen überall mehr. Es ist weniger mental belastend und einfacher herauszufinden, was vor sich geht. Ich kann auch denselben Context Provider/Consumer wiederverwenden und ihn einfach funktionieren lassen.
2) Bricht sie die Datenfluss-Transparenz?
Ich würde sagen ja, sie bricht sie irgendwie. Besonders wenn Sie tief verschachtelte Komponenten haben, könnte es mühsam sein, nach oben zu gehen und zu verfolgen, wo der Context definiert wird. Man könnte argumentieren, dass dies genauso mental belastend ist wie Prop-Drilling.
Hoffentlich haben Sie in diesen Fällen den Context Provider irgendwo höher im Komponentenbaum oder zumindest mehrere Provider in einer einzigen Datei verwendet, um einfachere Updates zu ermöglichen.
3) Woher weiß ich, woher die Daten stammen?
Ich würde sagen, es kommt darauf an, was Sie erreichen wollen. Wenn Sie eine komplexe Anwendung entwickeln, sollten Sie wahrscheinlich etwas wie Redux verwenden (das im Hintergrund ebenfalls Context verwendet).
Context kann, zumindest für mich, für eher einmalige Anwendungsfälle verwendet werden. Das Ändern des Themas einer App ist ein gängiger Anwendungsfall für die Verwendung der Context-API.
4) Warum nicht Komponenten weitergeben?
Das ist ein wichtiger Punkt, und das kann oft getan werden. Ich würde sagen, ein Problem mit diesem Ansatz ist jedoch, dass Komponenten tief in andere verschachtelt sind. Wir haben oft keinen Zugriff auf Komponenten tief in einem Baum, um dies zu tun, und das korrekte Verschachteln von Komponenten in jedem hypothetischen UI-Zustand wäre viel schwieriger als das Umschließen mit Context und das Reflektieren basierend auf diesem Context.