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 次