React Compound Components:‘Build Your Own Adventure’模式

发布: (2026年2月16日 GMT+8 19:07)
5 分钟阅读
原文: Dev.to

Source: Dev.to

请提供您希望翻译的具体文本内容,我将为您翻译成简体中文并保持原有的格式、Markdown 语法以及技术术语不变。谢谢!

🎨 图形化解释

“Mega‑Prop” 方式(旧方式) 🦖

你传入一个巨大的配置对象。组件自行决定布局。你完全没有控制权。如果想把 Tab List 移到 Panels 下面,就必须重写库。

+-----------------------------+
|       |
+-----------------------------+
| [Tab 1] [Tab 2] [Tab 3]     |
   {/* You put this where YOU want */}
    One
    Two


    Content 1
    Content 2

父组件 (Tabs) 通过 Context 暗中把状态传递给子组件。它们之间进行心灵感应式的通信。 🧠✨

💻 代码:从“属性地狱”到“组件天堂”

1️⃣ 设置 – 创建上下文

首先,我们需要一个秘密通道,让组件之间进行通信。

import React, { createContext, useContext, useState } from 'react';

// 1. Create the Context
const AccordionContext = createContext();

// 2. Create the Parent Component
const Accordion = ({ children }) => {
  const [openIndex, setOpenIndex] = useState(0);

  // The toggle function logic
  const toggleIndex = (index) => {
    setOpenIndex(prev => (prev === index ? null : index));
  };

  return (
    {/* Provide context to children */}
    {children}
  );
};

2️⃣ 子组件 – 使用上下文

现在我们创建子组件。请注意,它们不接受用户传入的 isOpenonClick 属性,而是从上下文中获取。

// 3. The Item Component (Just a wrapper, usually)
const AccordionItem = ({ children }) => {
  return {children};
};

// 4. The Trigger (The clickable part)
const AccordionHeader = ({ children, index }) => {
  const { toggleIndex, openIndex } = useContext(AccordionContext);
  const isActive = openIndex === index;

  return (
    <div onClick={() => toggleIndex(index)}>
      {children} {isActive ? '🔽' : '▶️'}
    </div>
  );
};

// 5. The Content (The hidden part)
const AccordionPanel = ({ children, index }) => {
  const { openIndex } = useContext(AccordionContext);

  if (openIndex !== index) return null;

  return <div>{children}</div>;
};

// Attach sub‑components to the Parent for cleaner imports (optional but cool)
Accordion.Item = AccordionItem;
Accordion.Header = AccordionHeader;
Accordion.Panel = AccordionPanel;

export default Accordion;

3️⃣ 使用 – 魔法时刻 🪄

看看这个 API 有多简洁。没有复杂的配置对象,只有 JSX。

import Accordion from './Accordion';

const FAQ = () => (
  <Accordion>
    <Accordion.Item>
      <Accordion.Header index={0}>Is React hard?</Accordion.Header>
      <Accordion.Panel index={0}>
        Only until you understand `useEffect`. Then it's just chaotic.
      </Accordion.Panel>
    </Accordion.Item>

    <Accordion.Item>
      <Accordion.Header index={1}>Why use Compound Components?</Accordion.Header>
      <Accordion.Panel index={1}>
        Because passing 50 props is bad for your blood pressure.
      </Accordion.Panel>
    </Accordion.Item>
  </Accordion>
);

🏆 为什么要这样做?(使用场景)

  1. UI 库(标签页、选择框、菜单)
    如果你在构建设计系统,这一点是必不可少的。用户会想在 Tab 中放置图标,或将标签移到输入框下方。复合组件让他们无需添加 renderLabelBottom 属性即可实现。

  2. 隐式状态共享
    注意 Accordion.Header 如何自动知道如何切换面板吗?用户不必手动编写 onClick={() => setIndex(1)}。它自然而然工作。

  3. 语义结构
    它的写法像 HTML:“。声明式且美观。

🕳️ 陷阱(“坑点”)

  1. “单子组件”限制
    如果你将子组件包裹在一个 “ 中,阻断了上下文(在 Context API 中很少见,但在旧的 React.Children.map 方法中很常见),就会出错。
    解决方案: 采用上面展示的 Context API 模式。它能像 X‑射线一样穿透 div。

  2. 过度设计
    不要为一个简单的按钮使用它。如果组件没有需要在多个不同子组件之间共享的内部状态,你可能只需要普通的 props。

  3. 命名污染
    导出 Accordion, AccordionItem, AccordionHeader, AccordionPanel 会导致导入变得凌乱。
    解决方案: 将它们挂载到主组件上(Accordion.Item)。这样可以保持命名空间整洁,并让你看起来更专业。

🏁 结论

复合组件模式 就像是给人一条鱼(刚性的组件)和教会他们钓鱼(灵活的工具集合)之间的区别。

它需要你(库的作者)编写更多的初始化代码,但它为用户提供了更加人性化且可扩展的 API。

It's a delightful experience for the developer using your component.  

And isn't that what we all want? To be loved by other developers? (Please validate me). 🥺🤝
0 浏览
Back to Blog

相关文章

阅读更多 »