【发布时间】: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}
</>
)
}
我能想到的唯一方法是让Accordion 在children 更改时运行效果,然后深入遍历children 并找到AccordionSection 组件,同时不递归任何嵌套的Accordion 组件-- 然后cloneElement() 并注入context 作为每个AccordionSection 的道具。
这似乎不仅效率低下,而且我什至不完全确定它会起作用。这取决于效果运行时children 是否完全水合,我不确定是否会发生这种情况,并且它还需要在深度子级发生变化时调用Accordion 的渲染器,这我也不确定.
我目前的方法是为实现 Accordion 的开发人员创建一个自定义挂钩。该钩子返回一个函数,该函数返回必须手动传递给每个渲染的AccordionSection 的isOpen 和onToggle 函数。它可以工作并且可能比儿童解决方案更优雅,但需要更多开销,因为开发人员需要使用挂钩来维护原本封装在 Accordion 中的状态。
【问题讨论】:
-
不知道为什么要在父级而不是外部创建上下文...(或者为什么用
useMemo包装它) -
我同意@Sagivb.g。为什么要将其保留在组件内?把它移到外面,导出它,然后由其他组件导入它
-
context 不仅适用于父母对孩子,还可以在任何地方使用。只需将其视为状态包装器。
-
关键是没有什么是全球独一无二的,就像每个 Accordion 都保持自己的状态,哪些部分是打开的。手风琴的多个实例需要它们自己的上下文。但是 React 上下文的本质是,当“根”组件被渲染时,你不能即时实例化新的上下文实例。
-
我认为这不是在 React 中可以实际完成的事情。例如,如果您想要在应用程序中使用更多的 Redux 存储,Redux 要求您在从非默认存储读取的每个连接组件上指定存储的名称。所以我认为需要相同的方法,即如果一个组件服务于多个上下文,则子组件需要提供唯一标识符,而不仅仅是对其最近的父提供者的上下文进行魔法访问。在这里做了一个 POC:codesandbox.io/s/sweet-hofstadter-b42iy
标签: javascript reactjs react-context