48 votes

Réagir à des contextes multiples

J'utilise des fonctions qui sont transmises par le contexte.

ChildComponent.contextType = SomeContext;

J'utilise maintenant this.context.someFunction(); . Cela fonctionne.

Comment puis-je procéder si j'ai besoin de fonctions provenant de deux composants parents différents ?

92voto

coreyward Points 26109

Vous pouvez toujours utiliser des nœuds de consommateurs de type fonction-enfant avec l'API contextuelle 16.3, ce qui est le cas de la fonction La documentation de React suggère de faire :

// Theme context, default to light theme
const ThemeContext = React.createContext('light');

// Signed-in user context
const UserContext = React.createContext({
  name: 'Guest',
});

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

    // App component that provides initial context values
    return (
      <ThemeContext.Provider value={theme}>
        <UserContext.Provider value={signedInUser}>
          <Layout />
        </UserContext.Provider>
      </ThemeContext.Provider>
    );
  }
}

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

// A component may consume multiple contexts
function Content() {
  return (
    <ThemeContext.Consumer>
      {theme => (
        <UserContext.Consumer>
          {user => (
            <ProfilePage user={user} theme={theme} />
          )}
        </UserContext.Consumer>
      )}
    </ThemeContext.Consumer>
  );
}

Pour utiliser les fonctions en contexte dans votre composant, vous devez généralement envelopper votre composant dans un HOC afin que le contexte soit transmis en tant qu'accessoire :

export const withThemeContext = Component => (
  props => (
    <ThemeContext.Consumer>
      {context => <Component themeContext={context} {...props} />}
    </ThemeContext.Consumer>
  )
)

const YourComponent = ({ themeContext, ...props }) => {
  themeContext.someFunction()
  return (<div>Hi Mom!</div>)
}

export default withThemeContext(YourComponent)

Si vous utilisez React 16.8+, vous pouvez également utiliser des crochets pour faire cela plus proprement sans utiliser de HOC :

import React, { useContext } from "react"

const YourComponent = props => {
  const theme = useContext(ThemeContext)
  const user = useContext(UserContext)
}

Ou, si vous consommez souvent ces contextes, vous pouvez même créer un crochet personnalisé pour simplifier davantage :

const useTheme = () => useContext(ThemeContext)
const useUser = () => useContext(UserContext)

const YourComponent = props => {
  const theme = useTheme()
  const user = useUser()
}

14voto

Dimitar Points 339

Une autre solution consiste à créer un contexte distinct fournissant les autres contextes :

import React, { createContext, memo, useContext } from "react";
import isEqual from "react-fast-compare";

export const MultiContext = createContext(null);
MultiContext.displayName = "MultiContext";

export const MultiContextProvider = memo(
  function({ map, children }) {
    const contextMap = {};
    for (const i in map) {
      contextMap[i] = useContext(map[i]);
    }

    return (
      <MultiContext.Provider value={contextMap}>
        {children}
      </MultiContext.Provider>
    );
  },
  (prevProps, nextProps) => isEqual(prevProps.children, nextProps.children)
);

MultiContextProvider.displayName = "MultiContextProvider";

Exemple d'utilisation :

class DemoConsumer extends React.Component {
  static contextType = MultiContext;

  render() {
    return JSON.stringify({
      someValue: this.context.SomeContext.someValue,
      otherValue: this.context.OtherContext.otherValue,
    });
  }
}

function App() {
  return (
    <MultiContextProvider map={{ SomeContext, OtherContext }}>
      <MultiContextDemoClassConsumer />
    </MultiContextProvider>
  );
}

Démonstration :

const {
  createContext,
  memo,
  useContext,
  useState,
  useEffect,
} = React;

const MultiContext = createContext(null);
MultiContext.displayName = "MultiContext";

const MultiContextProvider = memo(
  function({ map, children }) {
    console.log("render provider");
    const contextMap = {};
    for (const i in map) {
      contextMap[i] = useContext(map[i]);
    }

    return (
      <MultiContext.Provider value={contextMap}>
        {children}
      </MultiContext.Provider>
    );
  },
  (prevProps, nextProps) => isEqual(prevProps.children, nextProps.children)
);
MultiContextProvider.displayName = "MultiContextProvider";

const initialMinutes = new Date().getMinutes();
const MinutesContext = createContext(initialMinutes);
MinutesContext.displayName = "MinutesContext";

const IncrementContext = createContext(0);
IncrementContext.displayName = "IncrementContext";

class MultiContextDemoClassConsumer extends React.Component {
  static contextType = MultiContext;

  render() {
    return JSON.stringify(this.context);
  }
}

const multiContextMap = { MinutesContext, IncrementContext };
function App() {
  const forceUpdate = useForceUpdate();

  const [minutes, setMinutes] = useState(initialMinutes);
  useEffect(() => {
    const timeoutId = setInterval(() => {
      // console.log('set minutes')
      setMinutes(new Date().getMinutes());
    }, 1000);
    return () => {
      clearInterval(timeoutId);
    };
  }, [setMinutes]);

  const [increment, setIncrement] = useState(0);

  console.log("render app");

  return (
    <MinutesContext.Provider value={minutes}>
      <IncrementContext.Provider value={increment}>
        <MultiContextProvider map={multiContextMap}>
          <MultiContextDemoClassConsumer />
        </MultiContextProvider>
        <button onClick={() => setIncrement(i => i + 1)}>Increment</button>
        <button onClick={forceUpdate}>Force Update</button>
      </IncrementContext.Provider>
    </MinutesContext.Provider>
  );
}

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

<script type="module">
  import React from 'https://dev.jspm.io/react@16';
  import ReactDOM from 'https://dev.jspm.io/react-dom@16';
  import useForceUpdate from 'https://dev.jspm.io/use-force-update@1.0.7';
  import isEqual from 'https://dev.jspm.io/react-fast-compare@3.0.1';
  window.React = React;
  window.ReactDOM = ReactDOM;
  window.useForceUpdate = useForceUpdate.default;
  window.isEqual = isEqual;
</script>
<div id="root"></div>

8voto

Gaspard Bucher Points 1283

Vous pouvez également fusionner tous vos contextes en un seul :

const AppContext = React.createContext({
  user: { name: 'Guest' },
  theme: 'light',
})

ChildComponent.contextType = AppContext;

Fait. Il vous suffit de fusionner les nouvelles valeurs si vous avez un contexte différent dans certaines parties de votre application (comme un thème ou un utilisateur différent).

23 votes

En ce qui concerne votre solution. Elle effectue des rendus supplémentaires lorsque la valeur du contexte change dans un objet imbriqué, n'est-ce pas ? L'utilisateur et le thème peuvent tous deux avoir des objets imbriqués et cela provoque des re-rendus partout après un changement d'objet (même là où ce n'était pas nécessaire), sauf erreur de ma part. Corrigez-moi si je me trompe

1 votes

@sunpietro si la valeur du contexte change, tout composant qui utilise le contexte sera rendu à nouveau, oui. C'est pourquoi il est logique d'avoir un contexte qui change rarement au sommet de l'arbre et un qui change beaucoup plus près de l'endroit où il est utilisé.

0 votes

Votre solution est très bonne, MAIS pour éviter l'excès de rendu, nous pouvons utiliser le crochet Reducer pour créer un seul contexte.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X