Zugriff auf Reaktionskontext außerhalb der Renderfunktion

91

Ich entwickle eine neue App mit der neuen React Context-API anstelle von Redux. Bevor Reduxich beispielsweise eine Benutzerliste abrufen musste, rufe ich einfach componentDidMountmeine Aktion auf, aber jetzt mit React Context leben meine Aktionen im Inneren Mein Consumer befindet sich in meiner Renderfunktion. Dies bedeutet, dass jedes Mal, wenn meine Renderfunktion aufgerufen wird, meine Aktion aufgerufen wird, um meine Benutzerliste abzurufen. Dies ist nicht gut, da ich viele unnötige Anforderungen ausführen werde.

Also, wie kann ich meine Aktion nur einmal componentDidMountaufrufen , wie in anstatt Render aufzurufen?

Schauen Sie sich zur Veranschaulichung diesen Code an:

Nehmen wir an, ich verpacke alle meine ProvidersKomponenten wie folgt in eine Komponente:

import React from 'react';

import UserProvider from './UserProvider';
import PostProvider from './PostProvider';

export default class Provider extends React.Component {
  render(){
    return(
      <UserProvider>
        <PostProvider>
          {this.props.children}
        </PostProvider>
      </UserProvider>
    )
  }
}

Dann habe ich diese Provider-Komponente so eingefügt, dass sie meine gesamte App umschließt:

import React from 'react';
import Provider from './providers/Provider';
import { Router } from './Router';

export default class App extends React.Component {
  render() {
    const Component = Router();
    return(
      <Provider>
        <Component />
      </Provider>
    )
  }
}

In der Ansicht meiner Benutzer sieht es beispielsweise so aus:

import React from 'react';
import UserContext from '../contexts/UserContext';

export default class Users extends React.Component {
  render(){
    return(
      <UserContext.Consumer>
        {({getUsers, users}) => {
          getUsers();
          return(
            <h1>Users</h1>
            <ul>
              {users.map(user) => (
                <li>{user.name}</li>
              )}
            </ul>
          )
        }}
      </UserContext.Consumer>
    )
  }
}

Was ich will ist folgendes:

import React from 'react';
import UserContext from '../contexts/UserContext';

export default class Users extends React.Component {
  componentDidMount(){
    this.props.getUsers();
  }

  render(){
    return(
      <UserContext.Consumer>
        {({users}) => {
          getUsers();
          return(
            <h1>Users</h1>
            <ul>
              {users.map(user) => (
                <li>{user.name}</li>
              )}
            </ul>
          )
        }}
      </UserContext.Consumer>
    )
  }
}

Aber natürlich funktioniert das obige Beispiel nicht, da die getUsersRequisiten nicht in der Ansicht meiner Benutzer angezeigt werden. Was ist der richtige Weg, wenn dies überhaupt möglich ist?

Gustavo Mendonça
quelle

Antworten:

144

BEARBEITEN: Mit der Einführung von React -Hooks in Version 16.8.0 können Sie den Kontext in Funktionskomponenten verwenden, indem Sie useContextHook verwenden

const Users = () => {
    const contextValue = useContext(UserContext);
    // rest logic here
}

BEARBEITEN : Ab Version 16.6.0 . Sie können den Kontext in der Lebenszyklusmethode mit this.contextlike verwenden

class Users extends React.Component {
  componentDidMount() {
    let value = this.context;
    /* perform a side-effect at mount using the value of UserContext */
  }
  componentDidUpdate() {
    let value = this.context;
    /* ... */
  }
  componentWillUnmount() {
    let value = this.context;
    /* ... */
  }
  render() {
    let value = this.context;
    /* render something based on the value of UserContext */
  }
}
Users.contextType = UserContext; // This part is important to access context values

Vor Version 16.6.0 konnten Sie dies folgendermaßen tun

Um Context in Ihrer Lebenszyklusmethode zu verwenden, würden Sie Ihre Komponente wie folgt schreiben

class Users extends React.Component {
  componentDidMount(){
    this.props.getUsers();
  }

  render(){
    const { users } = this.props;
    return(

            <h1>Users</h1>
            <ul>
              {users.map(user) => (
                <li>{user.name}</li>
              )}
            </ul>
    )
  }
}
export default props => ( <UserContext.Consumer>
        {({users, getUsers}) => {
           return <Users {...props} users={users} getUsers={getUsers} />
        }}
      </UserContext.Consumer>
)

Im Allgemeinen würden Sie einen Kontext in Ihrer App beibehalten, und es ist sinnvoll, das oben genannte Login in einem HOC zu verpacken, um es wiederzuverwenden. Sie können es wie schreiben

import UserContext from 'path/to/UserContext';

const withUserContext = Component => {
  return props => {
    return (
      <UserContext.Consumer>
        {({users, getUsers}) => {
          return <Component {...props} users={users} getUsers={getUsers} />;
        }}
      </UserContext.Consumer>
    );
  };
};

und dann kannst du es gerne benutzen

export default withUserContext(User);
Shubham Khatri
quelle
Das ist ein Weg, es zu tun! Aber ich verpacke meine Komponenten mit anderen Dingen, so dass der Code auf diese Weise immer komplizierter wird. Die Verwendung der With-Context-Bibliothek und ihrer Dekoratoren vereinfacht den Code erheblich. Aber danke für die Antwort!
Gustavo Mendonça
Es funktioniert nicht für mich, ich benutze den With-Context-Github
PassionateDeveloper
1
Shubham Khatri hat in seiner Antwort Recht, es gibt nur einen kleinen Tippfehler, der dies verhindert. Die geschweiften Klammern verhindern die implizite Rückkehr. Ersetzen Sie sie einfach durch Klammern.
VDubs
In der Kontext-API-Dokumentation heißt es: „[Sie können this.contextin jeder der Lebenszyklusmethoden auf [ ] verweisen ] ( reactjs.org/docs/context.html#classcontexttype )“, aber Sie implizieren, dass dies nicht der Fall ist? Ihr Code scheint einige Anstrengungen zu unternehmen, um den Kontext erneut in Requisiten einzufügen, anstatt dort, wo die API dies vorschreibt.
Bignose
1
@ GustavoMendonça Mit der API-Implementierung this.context können Sie nur einen einzelnen Kontext abonnieren. Wenn Sie mehr als eine lesen müssen, müssen Sie dem Render-Requisitenmuster folgen
Shubham Khatri
3

Ok, ich habe einen Weg gefunden, dies mit einer Einschränkung zu tun. Mit demwith-context Bibliothek konnte ich alle meine Verbraucherdaten in meine Komponenten-Requisiten einfügen.

Das Einfügen von mehr als einem Consumer in dieselbe Komponente ist jedoch kompliziert. Sie müssen mit dieser Bibliothek gemischte Consumer erstellen, wodurch der Code nicht elegant und nicht produktiv wird.

Der Link zu dieser Bibliothek: https://github.com/SunHuawei/with-context

BEARBEITEN: Eigentlich müssen Sie nicht die Multi-Kontext-API verwenden, with-contextdie tatsächlich die einfache API verwendet und einen Dekorator für jeden Ihrer Kontexte erstellt. Wenn Sie mehr als einen Consumer in Ihrer Komponente verwenden möchten, müssen Sie nur erkläre über deiner Klasse so viele Dekorateure, wie du willst!

Gustavo Mendonça
quelle
1

Für meinen Teil war es genug, um .bind(this)die Veranstaltung zu ergänzen . So sieht meine Komponente aus.

// Stores File
class RootStore {
   //...States, etc
}
const myRootContext = React.createContext(new RootStore())
export default myRootContext;


// In Component
class MyComp extends Component {
  static contextType = myRootContext;

  doSomething() {
   console.log()
  }

  render() {
    return <button onClick={this.doSomething.bind(this)}></button>
  }
}
Kugelschreiber
quelle
1
Dieser Code sieht viel einfacher aus, greift jedoch nicht auf Werte zu und interagiert überhaupt nicht mit der RootStore-Instanz. Können Sie ein Beispiel zeigen, das auf zwei Dateien aufgeteilt ist, und reaktive UI-Updates?
dcsan
-7

Sie müssen den Kontext in der übergeordneten Komponente übergeben, um als Requisiten im untergeordneten Element Zugriff zu erhalten.

kprocks
quelle