人人都会AI编程

10.3 keep-alive 组件缓存

更新时间:2026-07-10

为什么需要组合与插槽

在 React 中,组件复用不仅仅是传递不同的 Props,更常见的需求是:一个容器组件需要渲染不同的内容,但这些内容的具体结构由使用方决定。例如,一个 Card 组件可能展示用户信息,也可能展示商品详情,或者嵌入一段富文本。

传统的做法是通过 Props 逐个传递数据,但这很快就会变得臃肿且不灵活。React 提供了一种更优雅的方式:利用 children 属性实现类似“插槽”的组合模式,让父组件只负责提供容器骨架,子内容由调用方任意填充。

children 的本质

children 是 React 组件中最特殊的 Props。当你在 JSX 中在组件标签内放置任何内容时,这些内容会自动作为 children 传入该组件。

function Container({ children }) {
  return <div className="container">{children}</div>;
}

<Container>
  <h1>标题</h1>
  <p>这是一段内容</p>
</Container>

编译后,Container 接收到的 children 就是一个包含 <h1><p> 的 React 元素数组。Container 不需要知道这些元素的任何细节,它只负责把它们放到 <div className="container"> 内部。这种模式被称为 “组合”(Composition)

基础插槽:单个 children

最简单的插槽就是整个组件只暴露一个 children 位置,调用方把任意 JSX 传入,容器进行包裹处理:

function DropdownMenu({ children }) {
  return <div className="dropdown-menu">{children}</div>;
}

// 使用
<DropdownMenu>
  <button>编辑</button>
  <button>删除</button>
</DropdownMenu>

容器 DropdownMenu 封装了样式、事件监听或动画逻辑,但内部的按钮完全由使用方定义。这是高内聚低耦合的典范。

多插槽模式:通过 Props 传递多个位置

当容器需要多个“预留位”时,单一的 children 就不够用了。这时可以通过多个 Props 来模拟命名插槽:

function Layout({ header, sidebar, children }) {
  return (
    <div className="layout">
      <header className="layout-header">{header}</header>
      <nav className="layout-sidebar">{sidebar}</nav>
      <main className="layout-content">{children}</main>
    </div>
  );
}

// 使用
<Layout
  header={<Logo />}
  sidebar={<MenuItems />}
>
  <Dashboard />
</Layout>

这里 Layout 定义了三个“插槽位置”:headersidebar 和默认的 children(主内容区)。用户可以按需填充,完全控制每个区域的内容,而不改变布局组件的逻辑。

带条件的插槽:根据内容做自适应

有时容器需要根据某个插入内容是否存在来调整 UI。你可以结合 Props 的类型检查来做条件渲染:

function Panel({ title, actions, children }) {
  return (
    <div className="panel">
      <div className="panel-header">
        <h2>{title}</h2>
        {actions && <div className="panel-actions">{actions}</div>}
      </div>
      <div className="panel-body">
        {children || <p className="empty">暂无内容</p>}
      </div>
    </div>
  );
}

// 使用
<Panel title="用户列表" actions={<Button>新增</Button>}>
  <UserTable />
</Panel>

Panel 根据 actionschildren 的存在与否,决定是否渲染对应区域,实现了自适应布局。这种模式在封装通用组件库时极其常见。

进阶:组合模式 vs 继承的对比

React 官方明确推荐使用组合而非继承来复用组件间的代码。在传统面向对象中,你可能通过创建子类来扩展组件功能;但在 React 中,通过组合和插槽,可以更灵活地复用组件。

继承的痛点:

  • 多层继承关系会让代码难以理解和修改。
  • 子类强依赖父类内部实现,是紧耦合。
  • React 组件本身是函数,使用继承违背其设计哲学。

组合的优势:

  • 容器与内容完全解耦,一方的变化不影响另一方。
  • 通过 children 或 Props 组合,无需关心抽象层级。
  • 更贴近 UI 搭建本身的“积木”思维。

实际案例:封装一个 Modal 对话框

一个通用的 Modal 组件需要渲染遮罩层、标题、内容和底部操作按钮。采用插槽模式:

function Modal({ isOpen, onClose, title, children, footer }) {
  if (!isOpen) return null;

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal-content" onClick={e => e.stopPropagation()}>
        <div className="modal-header">
          <h3>{title}</h3>
          <button onClick={onClose}>×</button>
        </div>
        <div className="modal-body">{children}</div>
        {footer && <div className="modal-footer">{footer}</div>}
      </div>
    </div>
  );
}

// 使用
<Modal
  isOpen={showModal}
  onClose={() => setShowModal(false)}
  title="编辑用户"
  footer={
    <>
      <button onClick={onCancel}>取消</button>
      <button onClick={onSave}>保存</button>
    </>
  }
>
  <UserForm user={selectedUser} />
</Modal>

Modal 只负责遮罩、定位、显示/隐藏逻辑和基本结构,内部的表单内容、底部按钮完全由使用者注入。任何需要弹窗的场景都可以复用这个 Modal,而不需要每次都重写弹窗逻辑。

组合模式的核心原则

  1. 容器不假设内容的具体类型:它只提供渲染位置和行为外壳。
  2. 内容可以是任意 JSX:包括其他组件、普通 HTML 元素、甚至 null
  3. 避免过度封装:如果某个容器把太多细节写死在里面,它就不再具有灵活的复用价值。保持“插槽”的开放性。

总结来说,children 和插槽模式是 React 组件设计中实现 “框架式封装” 的关键手段:你搭建好边界和规则,内容由使用方任意填充。这种模式既保证了组件功能的稳定,也保留了极大的定制空间,是现代 React 组件库设计的基石。