如何在上下文中存储项目列表而不将它们作为 React 中顶级组件的 props 传递?

问题描述 投票:0回答:1

我想知道这些 UI 库(如 Headless UIReact Aria Components 等)如何仅传递元素列表,例如

<Tab>
中的
<TabList>
组件和
<TabPanel>
组件中的
<TabPanels>
(对于无头 UI 选项卡组件),它们都可以正常工作,而无需在我们导入这些组件的父组件中设置任何状态。

我之前也尝试创建类似的组件,但我不知道如何获取要在其父组件上下文中的数组或对象中设置的子元素或组件的列表。例如,如果我将创建一个类似于

Headless UI
<TabGroup> 组件的
<TabGroup>
组件,那么我将在我的项目中使用它,如下所示。

<TabGroup>
  <TabList>
    <TabButton>Tab 1</TabButton>
    <TabButton>Tab 2</TabButton>
    <TabButton>Tab 3</TabButton>
  </TabList>

  <TabPanels>
    <TabPanel>Panel 1 Content</TabPanel>
    <TabPanel>Panel 2 Content</TabPanel>
    <TabPanel>Panel 3 Content</TabPanel>
  </TabPanels>
</TabGroup>

我只是不将这些选项卡保留在数组中,然后映射它们或将它们作为道具传递给

<TabGroup>
组件或其任何子组件。我只是像任何其他 HTML 元素一样声明它们。

现在我可以做的是在

<TabGroup>
组件中创建一个上下文,并在其中设置
activeTab
属性,每次单击其中一个
<TabButton>
组件时该属性都会更新。但现在我如何知道当
activeTab
设置为
0
1
或任何其他值时要显示哪个选项卡?

另外,如果我想要在

tabButtons
组件中创建的上下文中包含
tabPanels
数组和
<TabGroup>
数组怎么办?

我使用了选项卡示例,因为我现在想创建一个选项卡示例,但我在创建自定义

<Select>
组件和许多其他组件时遇到了这个问题。

由于某些原因,我没有使用这些第三方库:

  1. 这些不提供单独的组件。我必须下载整个库才能使用其中的 1 或 2 个组件。
  2. 有一些样式和标记自定义限制(据我感觉)。
  3. 如果我自己创建它们,我也感觉很好,但请告诉我是否应该使用这些库或创建自己的组件,因为我不喜欢使用 UI 库,而且我喜欢自己创建组件,这样我就能感受到所有权代码😅。

请帮我解决这个问题。

提前谢谢您。

我尝试了什么

我正在考虑将上下文中的

activeTab
属性设置为正在单击的按钮的索引,但是我如何知道正在单击的按钮的
index

reactjs react-hooks components custom-component
1个回答
0
投票

简短的回答是:

Context
。长答案:

让我们看看

Tabs
react-aria
的代码:https://github.com/adobe/react-spectrum/blob/main/packages/react-aria-components/src/Tabs.tsx

在第 316 行我们看到这个:

  return (
    <div
      {...domProps}
      ref={ref}
      data-focused={isFocused || undefined}
      data-focus-visible={isFocusVisible || undefined}
      // @ts-ignore
      inert={inertValue(!isSelected)}
      data-inert={!isSelected ? 'true' : undefined}>
      <Provider
        values={[
          [TabsContext, null],
          [TabListStateContext, null]
        ]}>
        <CollectionRendererContext.Provider value={DefaultCollectionRenderer}>
          {renderProps.children}
        </CollectionRendererContext.Provider>
      </Provider>
    </div>
  );

现在

Context
到底做什么或是什么并不重要。但我们知道,每当渲染
TabPanel
时,它的子级都会被包裹在
Context Provider
中。所以内在的孩子将能够使用那个上下文。正如我们在第 204 行看到的:

let state = useContext(TabListStateContext)!;

所以我们可以做同样的事情,尽管更简单:

const TabListContext = React.createContext({
    activeTab: 0,
    setActiveTab: (index) => {}
})

const TabGroup = (props) => {
    const { children } = props
    const [activeTab, setActiveTab] = React.useState(0)

    // add index prop to each child 
    const tabs = React.Children.map(children, (child, index) => {
        return React.cloneElement(child, { index })
    })

    return (<div>
        <TabListContext.Provider value={{ activeTab, setActiveTab }}>
            { tabs }
        </TabListContext.Provider>
    </div>)
}

const Tab = (props) => {
    const { activeTab, setActiveTab } = React.useContext(TabListContext)
    const { index, title } = props
    const isActive = activeTab === index

   return (<div onClick={() => setActiveTab(index)}>
       {title} ({ isActive ? 'Active' : 'Inactive' })
   </div>)
}

const App = () => {
    return (<TabGroup>
        <Tab title="First tab" />
        <Tab title="Second tab" />
    </TabGroup>)
}

ReactDOM.render(<App />, document.getElementById("container"))
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
<div id="container"></div>

运行代码片段并单击选项卡以查看活动状态更改

© www.soinside.com 2019 - 2024. All rights reserved.