react多个上下文

IT技术 reactjs
2021-04-13 12:25:44

我正在使用通过上下文传递的函数。

ChildComponent.contextType = SomeContext;

现在我使用this.context.someFunction();. 这有效。

如果我需要来自两个不同父组件的函数,我该怎么做?

4个回答

您仍然可以将 function-as-a-child 消费者节点与 16.3 Context API 一起使用,这是React 文档建议执行的操作

// 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>
  );
}

要在组件的上下文中使用函数,您通常会将组件包装在 HOC 中,以便将上下文作为 props 传入:

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)

如果您运行的是 React 16.8+,您还可以使用钩子更干净地完成此操作,而无需使用 HOC:

import React, { useContext } from "react"

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

或者,如果你经常使用这些上下文,你甚至可以制作一个自定义钩子来进一步简化:

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

const YourComponent = props => {
  const theme = useTheme()
  const user = useUser()
}
什么时候YourComponent是一个类,然后themeContext变成this.props.themeContext,对吧?
2021-05-24 12:25:44
上下文给了我在类中使用的函数,而不是要呈现的数据。
2021-06-04 12:25:44
@ATOzTOA 这对多个上下文提供程序的工作方式与正常情况相同,但我用一个简单的例子更新了我的答案。
2021-06-10 12:25:44
如果我真的需要将这些上下文彼此分开,我也会使用这种方法
2021-06-15 12:25:44
我特别喜欢使用withThemeContext HOC :)
2021-06-17 12:25:44

另一种解决方案是创建一个单独的上下文来提供其他上下文:

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";

用法示例:

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>
  );
}

演示:

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>

您也可以简单地将所有上下文合并为一个:

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

ChildComponent.contextType = AppContext;

完毕。如果您的应用程序的某些部分(例如不同的主题或用户)具有不同的上下文,则只需合并新值。

考虑您的解决方案。当某些嵌套对象中的上下文值发生变化时,它会进行额外的重新渲染,不是吗?用户和主题都可以有嵌套对象,并且在更改对象后会导致到处重新渲染(即使不需要),除非我弄错了。如果我错了,请纠正我
2021-05-22 12:25:44
@sunpietro 如果上下文的值发生变化,任何利用上下文的组件都将重新渲染,是的。这就是为什么在树的顶部设置一个不经常更改的上下文并且在更接近它的使用位置更改很多的上下文是有意义的。
2021-05-23 12:25:44
有人可以解释为什么这个解决方案不起作用吗?
2021-05-28 12:25:44
您的解决方案非常好,但是为了避免渲染过多,我们可能会使用 useReducer 钩子来创建一个上下文。
2021-05-29 12:25:44
@HoussemBadri 也许您可以发布一个答案来证明您的建议?
2021-06-01 12:25:44

这对我有用。

<AuthProvider>
      <ProvideSide>
        <Component {...pageProps} />
      </ProvideSide>
    </AuthProvider>

我所做的只是确保我在 authprovider 和 provider 上下文中传递子项。

authprovider 上下文函数

export function AuthProvider({ children }) {
  const auth = useProvideAuth();

  return <authContext.Provider value={auth}>{children}</authContext.Provider>;
}

提供方内容

export function ProvideSide({ children }) {
  const side = useProvideSide();
  return <sideContext.Provider value={side}>{children}</sideContext.Provider>;
}