【问题标题】: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

标签: javascript reactjs react-context


【解决方案1】:

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

  1. 提供者
  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() 对象,但只要每个手风琴都包装在自己的提供程序中,每个手风琴都会有自己的上下文数据。这很有意义,并使这成为“正常”方式成为可能。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-30
  • 2010-10-27
  • 2023-01-05
  • 1970-01-01
相关资源
最近更新 更多