如何在单独安装的组件树之间共享React上下文状态

时间:2019-09-09 19:32:18

标签: javascript reactjs

我了解React Context对于在深层组件树中共享状态而不必进行prop钻探非常有用。但是,我找不到在多个单独的组件树之间共享相同上下文值的好方法。我使用的应用不是真正的React前端;相反,它在页面上散布着一些React树。我想使用上下文在它们之间共享状态。我知道Redux可以解决这个问题,但我尽量避免使用它。

// counter-context.jsx
import React, { useState, createContext } from 'react';

const CounterContext = createContext();

const CounterContextProvider = ({ children }) => {
  const [counter, setCounter] = useState(0);

  function increment() {
    setCounter(counter + 1);
  }

  return (
    <CounterContext.Provider value={{ counter, increment }}>
      {children}
    </CounterContext.Provider>
  );
};

export { CounterContextProvider, CounterContext };

// FirstComponent.jsx
import React, { useContext } from 'react';
import { render } from 'react-dom'; 

import { CounterContextProvider, CounterContext } from './CounterContext';

const FirstComponent = () => {
  const { counter } = useContext(CounterContext);

  return <p>First component: {counter}</p>;
};

render(
  <CounterContextProvider>
    <FirstComponent />
  </CounterContextProvider>,
  document.getElementById('first-component')
);

// SecondComponent.jsx
import React, { useContext } from 'react';
import { render } from 'react-dom';

import CounterContext from './CounterContext';

const SecondComponent = () => {
  const { counter } = useContext(CounterContext);

  return <p>Second component: {counter}</p>;
};

render(
  <CounterContextProvider>
    <SecondComponent />
  </CounterContextProvider>,
  document.getElementById('second-component')
);

如果我在FirstComponent中添加一个调用increment的按钮,我希望更新后的counter值也能反映在SecondComponent中。但是,鉴于创建了CounterContextProvider的新实例,我不确定如何实现。有没有相对干净的方法,还是我坚持使用Redux?

2 个答案:

答案 0 :(得分:0)

这两个组件可以使用react Portal呈现,因此它们都属于同一父对象。现在,将有一个提供程序可供两个组件使用

答案 1 :(得分:0)

正如 Hari Abinesh 提到的,如果您的用例不是太复杂,门户是解决这个问题的一种方法。

文档:https://reactjs.org/docs/portals.html

// counter-context.jsx
import React, { useState, createContext } from 'react';

const CounterContext = createContext();

const CounterContextProvider = ({ children }) => {
  const [counter, setCounter] = useState(0);

  function increment() {
    setCounter(counter + 1);
  }

  return (
    <CounterContext.Provider value={{ counter, increment }}>
      {children}
    </CounterContext.Provider>
  );
};

export { CounterContextProvider, CounterContext };

// FirstComponent.jsx
import React, { useContext } from 'react';
import { render, createPortal } from 'react-dom';
import { CounterContextProvider, CounterContext } from './CounterContext';

const FirstComponent = () => {
  const { counter } = useContext(CounterContext);
  const portalElement = document.getElementById('span-portal')
  return (
    <React.Fragment>
      <p>First component: {counter}</p>
      {createPortal(counter, portalElement)}
    </React.Fragment>
  );
};

render(
  <CounterContextProvider>
    <FirstComponent />
  </CounterContextProvider>,
  document.getElementById('first-component')
);

// SecondComponent.jsx
import React from 'react';
import { render } from 'react-dom';

const SecondComponent = () => {
  return <p>Second component: <span id='span-portal'></span> </p>;
};

// At this point, you might not even need context at all if it's only going
// to be rendered from one component.
render(
  <SecondComponent />,
  document.getElementById('second-component')
);

另一种方法是使用 react-relink。它比 Redux 简单得多,旨在跨不同的 React 组件树工作,您无需将组件包装在任何 <Provider> 中。也就是说,如果您愿意使用的不仅仅是内置的 React 解决方案。

// counter-context.jsx -> counter-source.jsx
import { createSource, useRelinkValue } from 'react-relink';

export const CounterSource = createSource({
  default: 1,
})

export function useCounterValue() {
  return useRelinkValue(CounterSource);
}

export function incrementCounter() {
  CounterSource.set(c => c + 1)
}

// FirstComponent.jsx
import React from 'react';
import { render } from 'react-dom';
import { useCounterValue } from './counter-source';

const FirstComponent = () => {
  const counter = useCounterValue();
  return <p>First component: {counter}</p>;
};

render(
  <FirstComponent />,
  document.getElementById('first-component')
);

// SecondComponent.jsx
import React from 'react';
import { render } from 'react-dom';
import { useCounterValue } from './counter-source';

const SecondComponent = () => {
  const counter = useCounterValue();
  return <p>Second component: {counter}</p>;
};

render(
  <SecondComponent />,
  document.getElementById('second-component')
);