反应多个上下文

Posted

技术标签:

【中文标题】反应多个上下文【英文标题】:React multiple contexts 【发布时间】:2019-04-20 03:36:04 【问题描述】:

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

ChildComponent.contextType = SomeContext;

现在我使用this.context.someFunction();。这行得通。

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

【问题讨论】:

【参考方案1】:

您仍然可以将函数作为子消费者节点与 16.3 上下文 API 一起使用,这就是 React documentation suggests doing:

// 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,对吧? 我特别喜欢使用withThemeContext HOC :) @ThunD3eR 不一定。当您在子项中使用上下文时,您会告诉 React 在上下文更改时重新渲染该组件。如果您有独立状态位,将多个状态位组合到一个上下文提供程序中可以增加上下文更改时必须呈现的组件数量。你真的必须权衡你发现自己所处的应用程序/场景的影响,因为没有没有权衡的“一条真正的道路”。 @ThunD3eR 是的,这是可能的,我鼓励您对其进行测试,看看哪些对您的应用程序有效。请注意,虽然 memoization 不是免费的,所以您可能会遇到 React.Memo 降低性能而不是渲染某些组件或树的情况,或者性能优势不值得用它构建的挑战(例如无意的错误,热重载问题, 等等)。最后,Recoil 可能值得考虑——它还处于早期阶段,但在某些情况下非常引人注目!【参考方案2】:

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

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>

【讨论】:

【参考方案3】:

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

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

ChildComponent.contextType = AppContext;

完成。如果您在应用的某些部分(如不同的主题或用户)有不同的上下文,您只需合并新值。

【讨论】:

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

这对我有用。

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

我所做的只是确保我在 authprovider 和提供上下文中传递孩子。

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

【讨论】:

以上是关于反应多个上下文的主要内容,如果未能解决你的问题,请参考以下文章

R Shiny:如何使多个元素在添加/删除按钮上下文中具有反应性?

React Native Context,如何在多个嵌套文件和组件之间共享上下文

传递给反应组件子级的函数不接收函数上下文

反应路由器dom v4 |上下文路由器和路由未定义

如何传递多个值以响应本机上下文 API

如何将反应上下文与反应导航一起使用