有啥实用的方法可以在组件中调用“React.createContext()”吗?

Posted

技术标签:

【中文标题】有啥实用的方法可以在组件中调用“React.createContext()”吗?【英文标题】:Is there any practical way to call `React.createContext()` within a component?有什么实用的方法可以在组件中调用“React.createContext()”吗? 【发布时间】:2020-05-16 09:13:40 【问题描述】:

假设我想为“手风琴”(一组可折叠面板)创建一个 UI 组件。父组件控制哪些面板打开的状态,而子面板应该能够读取上下文以确定它们是否打开。

const Accordion = ( children ) => 
  const [openSections, setOpenSections] = useState()

  const isOpen = sectionId => Boolean(openSections[sectionId])

  const onToggle = sectionId => () =>
    setOpenSections( ...openSections, [sectionId]: !openSections[sectionId] )

  const context = useMemo(() => createContext(), [])
    // Can't tell children to use *this* context

  return (
    <context.Provider value=useMemo(() => ( isOpen, onToggle ), [isOpen, onToggle])>
      children
    </context.Provider>
  )


const AccordionSection = ( sectionId, title, children ) => 
  const  isOpen, onToggle  = useContext(context)
    // No way to infer the right context

  return (
    <>
      <button onClick=onToggle(sectionId)>isOpen(sectionId) ? 'Close' : 'Open'</button>
      isOpen && children
    </>
  )

我能想到的唯一方法是让Accordionchildren 更改时运行效果,然后深入遍历children 并找到AccordionSection 组件,同时不递归任何嵌套的Accordion 组件-- 然后cloneElement() 并注入context 作为每个AccordionSection 的道具。

这似乎不仅效率低下,而且我什至不完全确定它会起作用。这取决于效果运行时children 是否完全水合,我不确定是否会发生这种情况,并且它还需要在深度子级发生变化时调用Accordion 的渲染器,这我也不确定.

我目前的方法是为实现 Accordion 的开发人员创建一个自定义挂钩。该钩子返回一个函数,该函数返回必须手动传递给每个渲染的AccordionSectionisOpenonToggle 函数。它可以工作并且可能比儿童解决方案更优雅,但需要更多开销,因为开发人员需要使用挂钩来维护原本封装在 Accordion 中的状态。

【问题讨论】:

不知道为什么要在父级而不是外部创建上下文...(或者为什么用useMemo 包装它) 我同意@Sagivb.g。为什么要将其保留在组件内?把它移到外面,导出它,然后由其他组件导入它 context 不仅适用于父母对孩子,还可以在任何地方使用。只需将其视为状态包装器。 关键是没有什么是全球独一无二的,就像每个 Accordion 都保持自己的状态,哪些部分是打开的。手风琴的多个实例需要它们自己的上下文。但是 React 上下文的本质是,当“根”组件被渲染时,你不能即时实例化新的上下文实例。 我认为这不是在 React 中可以实际完成的事情。例如,如果您想要在应用程序中使用更多的 Redux 存储,Redux 要求您在从非默认存储读取的每个连接组件上指定存储的名称。所以我认为需要相同的方法,即如果一个组件服务于多个上下文,则子组件需要提供唯一标识符,而不仅仅是对其最近的父提供者的上下文进行魔法访问。在这里做了一个 POC:codesandbox.io/s/sweet-hofstadter-b42iy 【参考方案1】:

React.createContext 将返回一个包含 2 个组件的对象:

    提供者 消费者

这两个组件可以共享数据,Consumer 可以从树上最近的Provider“抓取”上下文数据(或使用useContext 挂钩而不是渲染Consumer)。

您应该在父组件之外创建上下文对象,并使用它在您的children 组件内呈现Consumer(或使用useContext 挂钩)。

简单示例:

const myContext = createContext();

const Accordion = ( children ) => 
  // ...
  return (
    <myContext.Provider value=... >
      children
    </myContext.Provider>
  )



const AccordionSection = (...) => 
  const contextData = useContext(myContext);
  // use the data of your context as you wish
  // ...

请注意,我使用了useContext 钩子而不是渲染Consumer,如果你想使用钩子或Consumer,这取决于你。

您可以通过docs查看更多示例并获取更多详细信息

【讨论】:

谢谢,我遗漏了一些非常明显的东西!我的印象是createContext() 创建了一个包含奇异上下文值的奇异容器。直到刚才我才意识到每个 Provider 都有自己的价值;因此,多个手风琴将使用相同的createContext() 对象,但只要每个手风琴都包装在自己的提供程序中,每个手风琴都会有自己的上下文数据。这很有意义,并使这成为“正常”方式成为可能。谢谢!

以上是关于有啥实用的方法可以在组件中调用“React.createContext()”吗?的主要内容,如果未能解决你的问题,请参考以下文章

MUI Box 组件有啥用?

React 组件有啥方法可以保持之前渲染的值吗? [复制]

Angular 2+,有啥方法可以声明一个模块中的所有组件,然后在 app.module.ts 中导入?

有啥方法可以防止以角度破坏组件?

有啥方法可以通过“http 请求”和通过 DI 在 Blazor 组件中的“DI 范围”中为每个用户提供持久性?

有啥方法可以在我的 Java 游戏上调整我的组件大小?