Context

Context ermöglicht es, Daten durch den Komponentenbaum zu leiten, ohne die Props in jeder Schicht manuell zu übergeben.

In einer typischen React-Anwendung werden Daten top-down (Elternkomponente zurKindkomponente) durch Props übergeben. Für bestimmte Arten von Props (z.B. lokale Einstellungen, UI Theme), welche von vielen Komponenten innerhalb der Anwendung benötigt werden kann dies sehr umständlich werden. Mit Hilfe von Context ist es möglich, solche Werte zwischen Komponenten zu teilen, ohne diese explizit als Prop durch alle Schichten des Baumes zu geben.

Wann Context verwendet werden soll

Context wurde entwickelt, um Daten zu teilen, welche für einen Baum von React Komponenten als “global” bezeichnet werden können, zum Beispiel der aktuelle authentifizierte Nutzer, das Theme oder die bevorzugte Sprache. In dem unten angeführten Codebeispiel, leiten wir ein Theme Prop manuell durch den Code, um die Button-Komponente zu stylen:

class App extends React.Component {
  render() {
    return <Toolbar theme="dark" />;
  }
}

function Toolbar(props) {
  // Diese Toolbar-Komponente muss eine extra "theme" Prop annehmen  // und an ThemedButton übergeben. Das kann sehr umständlich werden,  // wenn jeder einzelne Button in der App über das Theme bescheid  // wissen muss, weil es durch alle Komponenten durchgegeben werden muss.  return (
    <div>
      <ThemedButton theme={props.theme} />    </div>
  );
}

class ThemedButton extends React.Component {
  render() {
    return <Button theme={this.props.theme} />;
  }
}

Mit der Verwendung von Context können wir das Übergeben der Props durch Zwischenelemente verhindern:

// Context lasst uns einen Wert tief durch den Komponenten-Baum// übergeben ohne ihn explizit durch jede Komponente durchzureichen.// Erstelle einen Context für das aktuelle Theme (mit "light" als Standardwert).const ThemeContext = React.createContext('light');
class App extends React.Component {
  render() {
    // Verwende einen Provider um das aktuelle Theme durch den Baum zu leiten.    // Jede Komponente kann es lesen, ganz egal wie tief sie liegt.    // In diesem Beispiel, übergeben wir "dark" als den aktuellen Wert.    return (
      <ThemeContext.Provider value="dark">        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// Eine Komponente in der Mitte braucht jetzt nicht// mehr explizit das Theme weitergeben.function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  // Weise einen contextType zu, um den aktuellen Theme Context zu lesen.  // React wird den nahestehensten Theme Provider darüber finden und dessen Wert lesen.  // In diesem Beispiel ist das aktuelle Theme "dark".  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;  }
}

Bevor du Context verwendest

Context wird hauptsächlich verwendet, wenn Daten von vielen Komponenten in verschiedenen genesteten Schichten erreichbar sein müssen. Benutze es sparsam, weil es das Wiederverwenden von Komponenten schwieriger macht.

Wenn du nur das Übergeben von Props durch mehrere Schichten verhindern möchtest, ist Komponenten-Komposition oft die bessere Lösung als Context.

Betrachte zum Beispiel eine Page Komponente, welche eine user und avatarSize Prop durch mehrere Schichten übergibt, damit die tief genestete Link und Avatar Komponenten sie lesen können:

<Page user={user} avatarSize={avatarSize} />
// ... rendert ...
<PageLayout user={user} avatarSize={avatarSize} />
// ... rendert ...
<NavigationBar user={user} avatarSize={avatarSize} />
// ... rendert ...
<Link href={user.permalink}>
  <Avatar user={user} size={avatarSize} />
</Link>

Es kann überflüßig erscheinen, die user und avatarSize Props durch so viele Schichten zu übergeben, wenn am Ende nur die Avatar Komponente sie wirklich benötigt. Außerdem ist es nervig, wenn die Avatar Komponente mehrere Props von oben benötigt, weil man diese dann auch in alle Zwischenschichten hinzufügen muss.

Der einzige Weg dieses Problem ohne Context zu lösen ist, die Avatar Komponente sich selber zu übergeben, damit die Zwischenkomponenten nichts über die user or avatarSize Props wissen müssen.

function Page(props) {
  const user = props.user;
  const userLink = (
    <Link href={user.permalink}>
      <Avatar user={user} size={props.avatarSize} />
    </Link>
  );
  return <PageLayout userLink={userLink} />;
}

// Jetzt haben wir:
<Page user={user} avatarSize={avatarSize} />
// ... rendert ...
<PageLayout userLink={...} />
// ... rendert ...
<NavigationBar userLink={...} />
// ... rendert ...
{props.userLink}

Mit dieser Veränderung muss nur die obereste Seitenkomponente über die Verwendung der user und avatarSize Props der Link und Avatar Komponenten Bescheid wissen.

Diese inversion of control (engl. für Umkehrung der Steuerung) macht den Code, durch das Reduzieren der Anzahl an überreichten Props durch die Anwendung und durch die Steigerung der Kontrolle der Root-Komponenten, in vielen Fällen sauberer. Jedoch ist das nicht in jedem Fall die richtige Entscheidung: durch das Schieben der Komplexität höher in den Baum, werden diese Higher-Level-Komponenten komplizierter und erzwingen die Lower-Level-Komponenten flexibler als erwünscht zu sein.

Du bist nicht auf ein einzelnes Kind für eine Komponente beschränkt. Du kannst mehrere Kinder durchgeben, sogar mehrere seperate Slots für Kinder haben, wie hier dokumentiert wird:

function Page(props) {
  const user = props.user;
  const content = <Feed user={user} />;
  const topBar = (
    <NavigationBar>
      <Link href={user.permalink}>
        <Avatar user={user} size={props.avatarSize} />
      </Link>
    </NavigationBar>
  );
  return (
    <PageLayout
      topBar={topBar}
      content={content}
    />
  );
}

Dieses Muster ist für viele Fälle ausreichend, in denen man ein Kind von seinen unmittelbaren Eltern entkoppeln möchte. Du kannst es noch einen Schritt mit ”render props” weitertreiben, wenn das Kind mit dem Elternteil vor dem Rendern kommunizieren muss.

Manchmal jedoch müssen die gleichen Daten von vielen Komponenten im Baum auf verschiedenen Schichten erreichbar sein. Mit Context kannst du solche Daten und dessen Veränderungen zu allen nachfolgenden Komponenten “broadcasten”. Gängige Beispiele, bei denen das Verwenden von Context einfacher ist als eine der Alternativen inkludieren das Managen von aktuellen lokalen Daten, Theme oder Daten Cache.

API

React.createContext

const MyContext = React.createContext(defaultValue);

Erstellt ein Context-Objekt. Wenn React eine Komponente rendert, die dieses Context-Objekt abonniert hat, wird es den aktuellen Context-Wert des am meisten übereinstimmenden Providers, welcher im Baum oberhalb des Wertes ist, lesen.

Das defaultValue Argument wird nur dann benützt, wenn eine Komponente keinen übereinstimmenden Provider im Baum oberhalb hat. Das kann für das isolierte Testen von Komponenten, ohne sie umwickeln zu müssen, hilfreich sein. Beachte: undefined als übergebener Providerwert verursacht nicht, dass defaultValue von konsumierenden Komponenten verwendet wird.

Context.Provider

<MyContext.Provider value={/* irgendein Wert */}>

Jedes Context-Objekt kommt mit einer Provider React Komponente, welche konsumierenden Komponenten erlaubt, die Veränderungen von Context zu abonnieren.

Ein value Prop wird von konsumierenden Komponenten akzeptiert, welche von dem Provider abstammen. Ein Provider kann mit vielen Konsumenten verbunden sein. Provider können genestet werden, um tiefer im Baum liegende Werte zu überschreiben.

Alle Konsumenten, welche von dem Provider abstammen, werden neu gerendert, wenn sich das value Prop des Providers ändert. Die Verbreitung des Providers zu dessen abstammenden Konsumenten (einschließlich .contextType und useContext) hängt nicht von der shouldComponentUpdate Methode ab, weshalb der Konsument ein Update bekommt, auch wenn eine zuvorkommende Komponente aus dem Update ausbricht.

Veränderungen werden mit dem Vergleichen des neuen und alten Werten ermittelt, welches den gleichen Algorithmus wie Object.is verwendet.

Hinweis

Diese Art und Weise wie Veränderungen ermittelt werden, kann Probleme schaffen, wenn Objekte als value überreicht werden: siehe Caveats

Class.contextType

class MyClass extends React.Component {
  componentDidMount() {
    let value = this.context;
    /* führe einen Nebeneffekt beim Mounten mit dem Wert von MyContext beim Mounten aus */
  }
  componentDidUpdate() {
    let value = this.context;
    /* ... */
  }
  componentWillUnmount() {
    let value = this.context;
    /* ... */
  }
  render() {
    let value = this.context;
    /* rendere etwas basierend auf dem Wert von MyContext */
  }
}
MyClass.contextType = MyContext;

Das contextType Prop einer Klasse kann mit React.createContext() ein Context-Objekt zugewiesen werden. Das lässt dich den nahestehensten aktuellen Wert des Context-Types, welcher this.context verwendet, konsumieren. Du kannst auf das in jeder Lifecycle-Methode, inklusive der Render-Funktion verweisen.

Beachte:

Du kannst nur einen einzigen Context mit dieser API abonnieren. Falls du mehrere benötigst, kannst du hier mehr darüber lesen: Konsumieren von mehreren Contexte

Falls du die experimentellen “public class fields syntax” verwendest, kannst du das static Klassen-Feld verwenden, um deinen contextType zu initialisieren.

class MyClass extends React.Component {
  static contextType = MyContext;
  render() {
    let value = this.context;
    /* rendere etwas auf dem Wert basierend */
  }
}

Context.Consumer

<MyContext.Consumer>
  {value => /* rendere etwas auf dem Context-Wert basierend */}
</MyContext.Consumer>

Eine React-Komponente die Context-Veränderungen abonniert hat. Das lässt dich einen Context innerhalb einer Funktions-Komponente abonnieren.

Benötigt eine Funktion als Kind. Diese Funktion erhält den aktuellen Context-Wert und gibt einen React-Knoten zurück. Das der Funktion übergebende value Argument wird mit dem value Prop des nahestehensten Provider von diesem Context im Baum darüber übereinstimmen. Falls es keinen Provider für diesen Context im Baum oberhalb gibt, wird das value Argument mit dem defaultValue, welches createContext() übergeben wurde, übereinstimmen.

Hinweis

Für nähere Informationen über das ‘Funkion als Kind’ Muster, siehe render props.

Context.displayName

Context-Objekt akzeptiert eine displayName String-Eigenschaft. React DevTools verwendet diesen String um festzustellen, was für den Context darzustellen ist.

Die folgende Komponente wird zum Beispiel als MyDisplayName in den DevTools erscheinen:

const MyContext = React.createContext(/* irgendein Wert */);
MyContext.displayName = 'MyDisplayName';

<MyContext.Provider> // "MyDisplayName.Provider" in DevTools
<MyContext.Consumer> // "MyDisplayName.Consumer" in DevTools

Beispiele

Dynamischer Context

Ein komplizierteres Beispiel mit dynamischen Werten für ein Theme:

theme-context.js

export const themes = {
  light: {
    foreground: '#000000',
    background: '#eeeeee',
  },
  dark: {
    foreground: '#ffffff',
    background: '#222222',
  },
};

export const ThemeContext = React.createContext(  themes.dark // Standardwert);

themed-button.js

import {ThemeContext} from './theme-context';

class ThemedButton extends React.Component {
  render() {
    let props = this.props;
    let theme = this.context;    return (
      <button
        {...props}
        style={{backgroundColor: theme.background}}
      />
    );
  }
}
ThemedButton.contextType = ThemeContext;
export default ThemedButton;

app.js

import {ThemeContext, themes} from './theme-context';
import ThemedButton from './themed-button';

// Eine Zwischen-Komponente die den ThemeButton verwendet
function Toolbar(props) {
  return (
    <ThemedButton onClick={props.changeTheme}>
      Change Theme
    </ThemedButton>
  );
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      theme: themes.light,
    };

    this.toggleTheme = () => {
      this.setState(state => ({
        theme:
          state.theme === themes.dark
            ? themes.light
            : themes.dark,
      }));
    };
  }

  render() {
    // Der ThemeButton Button innerhalb von ThemeProvider verwendet    // das Theme des States, währenddessen der Button außerhalb    // das Default "dark" Theme verwendet    return (
      <Page>
        <ThemeContext.Provider value={this.state.theme}>          <Toolbar changeTheme={this.toggleTheme} />        </ThemeContext.Provider>        <Section>
          <ThemedButton />        </Section>
      </Page>
    );
  }
}

ReactDOM.render(<App />, document.root);

Updaten des Contextes von einer genesteten Komponente

Oft ist es nötig, den Context von einer Komponente zu updaten, die sehr tief innerhalb des Komponenten-Baumes sitzt. In diesem Fall kannst du eine Funktion durch den Context übergeben, welche es allen Konsumenten ermöglicht, den Context zu updaten:

theme-context.js

// Stelle sicher, dass die Form des Standardwertes, welcher an createContext
// überreicht wird mit der Form die der Konsument erwartet, übereinstimmt!
export const ThemeContext = React.createContext({
  theme: themes.dark,  toggleTheme: () => {},});

theme-toggler-button.js

import {ThemeContext} from './theme-context';

function ThemeTogglerButton() {
  // Der Theme Toggler Button erhält nicht nur das Theme,   // sondern auch eine toggleTheme-Funktion von Context  return (
    <ThemeContext.Consumer>
      {({theme, toggleTheme}) => (        <button
          onClick={toggleTheme}
          style={{backgroundColor: theme.background}}>
          Toggle Theme
        </button>
      )}
    </ThemeContext.Consumer>
  );
}

export default ThemeTogglerButton;

app.js

import {ThemeContext, themes} from './theme-context';
import ThemeTogglerButton from './theme-toggler-button';

class App extends React.Component {
  constructor(props) {
    super(props);

    this.toggleTheme = () => {
      this.setState(state => ({
        theme:
          state.theme === themes.dark
            ? themes.light
            : themes.dark,
      }));
    };

    // State enthält auch die Update-Funktion, also wird    // es dem Context Provider überreicht.    this.state = {
      theme: themes.light,
      toggleTheme: this.toggleTheme,    };
  }

  render() {
    // Der gesamte State wird dem Provider überreicht.    return (
      <ThemeContext.Provider value={this.state}>        <Content />
      </ThemeContext.Provider>
    );
  }
}

function Content() {
  return (
    <div>
      <ThemeTogglerButton />
    </div>
  );
}

ReactDOM.render(<App />, document.root);

Konsumieren mehrerer Contexten

Um einem Context das schnelle neu Rendern beizubehalten, muss React für einen Context-Konsumenten einen eigenen Knoten im Baum erstellen.

// Theme Context, Standardwert ist "light"
const ThemeContext = React.createContext('light');

// Eingeloggter User-Context
const UserContext = React.createContext({
  name: 'Guest',
});

class App extends React.Component {
  render() {
    const {signedInUser, theme} = this.props;

    // App Komponente die die initialen Context-Werte bereitstellt
    return (
      <ThemeContext.Provider value={theme}>        <UserContext.Provider value={signedInUser}>          <Layout />
        </UserContext.Provider>      </ThemeContext.Provider>    );
  }
}

function Layout() {
  return (
    <div>
      <Sidebar />
      <Content />
    </div>
  );
}

// Eine Komponente kann mehrere Contexte konsumieren
function Content() {
  return (
    <ThemeContext.Consumer>      {theme => (        <UserContext.Consumer>          {user => (            <ProfilePage user={user} theme={theme} />          )}        </UserContext.Consumer>      )}    </ThemeContext.Consumer>  );
}

Falls zwei oder mehr Context-Werte oft gemeinsam verwendet werden, sollte man vielleicht überlegen, ob man eine eigene Render Prop Komponente erstellt, die beide Werte zur Verfügung stellt.

Fallen

Da Context eine Referenz-Identität verwendet, um festzustellen, wann es rerendern soll, gibt es einige Fallen die ungewollte Renders in Komsumten auslösen können, wenn ein Elternteil eines Providers rerendert. Der untenstehende Code zum Beispiel wird alle Konsumenten jedes Mal wenn der Provider rerendert, rerendern, weil jedes Mal ein neues Objekt für value erstellt wird:

class App extends React.Component {
  render() {
    return (
      <MyContext.Provider value={{something: 'something'}}>        <Toolbar />
      </MyContext.Provider>
    );
  }
}

Um dieses Problem zu umgehen, hebe den Wert in den State des Elternteils:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: {something: 'something'},    };
  }

  render() {
    return (
      <Provider value={this.state.value}>        <Toolbar />
      </Provider>
    );
  }
}

Legacy API

Hinweis

Früher hat React eine experimentelle Context API geliefert. Diese alte API wird von allen 16.x Releases supported, jedoch sollten alle Applikationen die es verwenden, zur neuesten Version migrieren. Diese Legacy API wird in zukünftigen React Versionen entfernt werden. Lese die Legacy Context Dokumentation hier.

Is this page useful?Bearbeite diese Seite