Есть ли способ заставить несколько потребителей контекста совместно использовать состояние?

Я использую React Context API, чтобы избежать бурения опор. Прямо сейчас мой Контекст включает useState и различные функции, которые обновляют состояние - они помещаются в объект const, который передается как value опора ActionsContext.Provider. Это абстракция моей текущей иерархии компонентов:

Header
---NavPanel
ContentContainer
---Content (Context.Consumer being returned in this component)

где Header и ContentContainer являются родственными элементами, а NavPanel и ContentContainer - их соответствующими дочерними элементами.

Сначала я поместил Context.Consumer в Content, потому что другим элементам он не нужен. Однако сейчас я создаю функцию, в которой NavPanel должна знать о состоянии, управляемом контекстом. Поэтому я поместил еще одного потребителя в NavPanel, только чтобы обнаружить, что отдельный потребитель означает отдельный экземпляр состояния.

Есть ли какой-нибудь умный обходной путь, который дает NavPanel и Content доступ к одному и тому же состоянию, который не предполагает помещения Consumer в родительский компонент Header и Content? Это привело бы к большому количеству буровых работ с учетом того, как мое приложение в настоящее время структурировано.

Пример нескольких экземпляров Codesandbox: https://codesandbox.io/s/context-multiple-consumers-v2wte


person badAtHooks    schedule 23.07.2019    source источник
comment
В зависимости от функции, попробуйте добавить пример того, что вы пытаетесь сделать, и какой подход к этому лучше всего подходит   -  person Dennis Vash    schedule 23.07.2019
comment
Я думаю, что имеет смысл поднять контекст выше. Одним из преимуществ использования контекста является то, что он помогает избежать бурения опор. Я использовал настраиваемый обработчик, чтобы легко использовать контекст в обоих компонентах, которые в нем нуждаются: codeandbox. io / s / context-множественные-потребители-d67cg   -  person go_diego    schedule 23.07.2019


Ответы (1)


Несколько вещей:

  • У вас должен быть только один провайдер для каждого штата, которым вы хотите поделиться.
      <ContextProvider>
        <PartOne />
        <hr />
        <PartTwo />
      </ContextProvider>

  • Лучше разделить ваш контекст на несколько контекстов, чтобы вы передавали значения вместо объектов. Таким образом, когда вы обновляете свое состояние, React обнаруживает, что оно другое, вместо того, чтобы сравнивать один и тот же объект.

  • Ваш ввод должен быть контролируемым компонентом https://reactjs.org/docs/forms.html

  • Рассмотрите возможность использования useContext API для лучшей эргономики, если вы используете React 16.8 вместо ContextConsumer.

С этими изменениями ваш код будет:

MyContext.js

import React, { useState } from "react";
export const MyItemContext = React.createContext();
export const MySetItemContext = React.createContext();
export const MyHandleKeyContext = React.createContext();

const ContextProvider = props => {
  const [itemBeingEdited, setItemBeingEdited] = useState("");
  const handleKey = event => {
    if (event.key === "Enter") {
      setItemBeingEdited("skittles");
    } else if (event.key === "K") {
      setItemBeingEdited("kilimanjaro");
    } else {
      setItemBeingEdited("");
    }
  };
  const editFunctions = {
    itemBeingEdited,
    setItemBeingEdited,
    handleKey
  };

  return (
    <MyItemContext.Provider value={itemBeingEdited}>
      <MyHandleKeyContext.Provider value={handleKey}>
        <MySetItemContext.Provider value={setItemBeingEdited}>
          {props.children}
        </MySetItemContext.Provider>
      </MyHandleKeyContext.Provider>
    </MyItemContext.Provider>
  );
};

export default ContextProvider;

PartOne.js

import React, { useContext } from "react";
import ContextProvider, {
  MyContext,
  MyItemContext,
  MySetItemContext,
  MyHandleKeyContext
} from "./MyContext";
const PartOne = () => {
  // blah
  const itemBeingEdited = useContext(MyItemContext);
  const handleKey = useContext(MyHandleKeyContext);
  const setItem = useContext(MySetItemContext);
  return (
    <React.Fragment>
      <span>{itemBeingEdited}</span>
      <input
        placeholder="Type in me"
        onKeyDown={handleKey}
        value={itemBeingEdited}
        onChange={e => setItem(e.target.value)}
      />
    </React.Fragment>
  );
};

export default PartOne;

PartTwo.js

import React, { useContext } from "react";
import ContextProvider, {
  MyContext,
  MyItemContext,
  MySetItemContext,
  MyHandleKeyContext
} from "./MyContext";
const PartTwo = () => {
  // blah

  const itemBeingEdited = useContext(MyItemContext);
  const handleKey = useContext(MyHandleKeyContext);
  const setItem = useContext(MySetItemContext);
  return (
    <React.Fragment>
      <span>{itemBeingEdited}</span>
      <input
        value={itemBeingEdited}
        type="text"
        placeholder="Type in me"
        onChange={e => setItem(e.target.value)}
        onKeyDown={handleKey}
      />
    </React.Fragment>
  );
};

export default PartTwo;

index.js

import React from "react";
import ReactDOM from "react-dom";
import PartOne from "./PartOne";
import PartTwo from "./PartTwo";
import ContextProvider from "./MyContext";
import "./styles.css";

function App() {
  return (
    <div className="App">

      <ContextProvider>
        <PartOne />
        <hr />
        <PartTwo />
      </ContextProvider>
    </div>
  );
}

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

CodeSandbox: https://codesandbox.io/s/context-multiple-consumers-vb9oj?fontsize=14.

person lissitz    schedule 23.07.2019