我想知道这些 UI 库(如 Headless UI、React 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>
组件和许多其他组件时遇到了这个问题。
由于某些原因,我没有使用这些第三方库:
请帮我解决这个问题。
提前谢谢您。
我正在考虑将上下文中的
activeTab
属性设置为正在单击的按钮的索引,但是我如何知道正在单击的按钮的 index
?
简短的回答是:
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>
运行代码片段并单击选项卡以查看活动状态更改