React 复合组件模式:用 Context 拆弹窗,告别层层透传 props
test12026-09-2110 次阅读
为什么需要复合组件
常见的弹窗、标签页组件往往用一个巨型 props 配置:把标题、内容、按钮全部塞进一个组件。当内部结构变化时,调用方被迫跟着改。复合组件(Compound Components)把组件拆成一组相互协作的子组件,由父组件通过 Context 共享状态,调用方像写 HTML 一样自由组合。
实现一个 Tabs 复合组件
父组件 Tabs 持有 active 状态并通过 Context 下发,Tab 与 TabPanel 作为子组件消费它。这样调用方可以任意插入自定义结构,而不必传入一长串配置。
import { createContext, useContext, useState, ReactNode } from 'react';
type TabsContextValue = {
active: string;
setActive: (id: string) => void;
};
const TabsContext = createContext<TabsContextValue | null>(null);
function useTabs() {
const ctx = useContext(TabsContext);
if (!ctx) throw new Error('Tabs 子组件必须在 <Tabs> 内使用');
return ctx;
}
export function Tabs({ defaultValue, children }: { defaultValue: string; children: ReactNode }) {
const [active, setActive] = useState(defaultValue);
return (
<TabsContext.Provider value={{ active, setActive }}>
{children}
</TabsContext.Provider>
);
}
export function TabList({ children }: { children: ReactNode }) {
return <div role="tablist" style={{ display: 'flex', gap: 8 }}>{children}</div>;
}
export function Tab({ id }: { id: string }) {
const { active, setActive } = useTabs();
return (
<button role="tab" aria-selected={active === id} onClick={() => setActive(id)}>
{id}
</button>
);
}
export function TabPanel({ id, children }: { id: string; children: ReactNode }) {
const { active } = useTabs();
if (active !== id) return null;
return <div role="tabpanel">{children}</div>;
}
调用方按需组合
子组件之间的关系由你决定,而不是由组件作者预先写死。组件库升级内部结构时,只要 Context 契约不变,调用方代码无需改动。
<Tabs defaultValue="a">
<TabList>
<Tab id="a" />
<Tab id="b" />
</TabList>
<TabPanel id="a">第一个面板内容</TabPanel>
<TabPanel id="b">第二个面板内容</TabPanel>
</Tabs>
何时使用
- 组件有明显的内外层级关系,例如 Tabs、Menu、Select、Accordion。
- 调用方需要自由控制子节点顺序与结构。
- 状态需要在兄弟子组件之间共享,但不想层层透传 props。
如果组件没有内部状态共享、或者子节点关系固定,直接用普通 props 反而更简单。复合组件解决的是状态共享与组合灵活性的问题,不要为了用而用。
T
test1
文章作者
为什么需要复合组件 常见的弹窗、标签页组件往往用一个巨型 props 配置:把标题、内容、按钮全部塞进一个组件。当内部...
- 分类
- 技术
- 发布时间
- 2026-09-21
- 字数
- 约 1992 字
- 阅读
- 10 次
