解析 React Composition Patterns

在 React 的世界里,有一个核心的设计哲学:组合优于继承(Composition over Inheritance)。React 官方极少推荐使用继承来构建组件,而是通过强大的组合模式(Composition Patterns)来实现代码的复用、解耦和灵活性。

下面为你详细拆解 React 中最常见且实用的几种组合模式,从基础到高级,帮助你在实际开发中游刃有余。


1. 基础组合:props.children(容器模式)

这是最直观、最常用的组合方式。组件作为一个"壳子"(Container),它不知道也不关心内部会渲染什么具体内容,而是将控制权交给父组件。

示例场景:弹窗(Dialog)或卡片(Card)

jsx 复制代码
// 子组件:作为一个通用的边框容器
function Card(props) {
  return (
    <div
      className="card-wrapper"
      style={{ border: '1px solid #ccc', padding: '20px' }}
    >
      {props.children} {/* 这里会渲染父组件传入的任何内容 */}
    </div>
  )
}

// 父组件:决定容器内部的具体内容
function App() {
  return (
    <Card>
      <h2>文章标题</h2>
      <p>这是卡片的内容主体...</p>
      <button>点赞</button>
    </Card>
  )
}

2. 插槽模式(Component Injection / Slots)

有时候,仅仅通过 props.children 顺次排列渲染是不够的。如果你的组件内部有多个特定的"预留位置"(比如网页的头部、侧边栏、主体内容),你可以直接将 React 元素(Components)作为普通的 props 传进去。这类似于 Vue 中的"具名插槽"。

示例场景:通用布局(Layout)

jsx 复制代码
// 布局组件
function AppLayout({ header, sidebar, content }) {
  return (
    <div className="layout-container">
      <header className="site-header">{header}</header>
      <div className="layout-body">
        <aside className="site-sidebar">{sidebar}</aside>
        <main className="site-main">{content}</main>
      </div>
    </div>
  )
}

// 使用布局
function App() {
  return (
    <AppLayout
      header={<NavigationMenu />}
      sidebar={<UserSidebar />}
      content={<DashboardMain />}
    />
  )
}

优势:打破了只能按上下顺序嵌套的限制,可以把组件精准放置在页面的任何结构中。


3. 复合组件模式(Compound Components)

这是编写高级 UI 组件库(如 Radix UI, Ant Design)最常用的黑魔法。它允许你创建一组相互关联的组件,这些组件共享隐式的状态,从而让使用者以极具可读性、声明式的方式编写 HTML。

示例场景:下拉菜单(Select)或标签页(Tabs)

如果没有复合组件,你可能需要写一个极其复杂的配置型组件:<Select options={[{...}, {...}]} onChange={...} />

而使用复合组件模式,你可以这样写:

jsx 复制代码
import React, { useState, createContext, useContext } from 'react'

// 1. 创建一个上下文用于共享状态
const ToggleContext = createContext()

// 2. 主组件(外层包裹)
function Toggle({ children }) {
  const [on, setOn] = useState(false)
  const toggle = () => setOn(!on)

  return (
    <ToggleContext.Provider value={{ on, toggle }}>
      {children}
    </ToggleContext.Provider>
  )
}

// 3. 子组件:开关按钮
Toggle.Button = function ToggleButton() {
  const { toggle } = useContext(ToggleContext)
  return <button onClick={toggle}>切换状态</button>
}

// 4. 子组件:开启时显示的内容
Toggle.On = function ToggleOn({ children }) {
  const { on } = useContext(ToggleContext)
  return on ? <div>{children}</div> : null
}

// 5. 子组件:关闭时显示的内容
Toggle.Off = function ToggleOff({ children }) {
  const { on } = useContext(ToggleContext)
  return !on ? <div>{children}</div> : null
}

// 使用它:极其直观和灵活!
function App() {
  return (
    <Toggle>
      <Toggle.Button />
      <Toggle.On>💡 灯亮了</Toggle.On>
      <Toggle.Off>❌ 灯滅了</Toggle.Off>
    </Toggle>
  )
}

优势 :调用者不需要手动在每个子组件之间传递 ontoggle 状态,组件结构清晰,扩展性极强。


4. 渲染属性模式(Render Props)

Render Props 模式是指:组件不自己决定渲染什么,而是接受一个返回 React 元素的函数,并将内部的状态作为参数传递给这个函数。

虽然现代 React 开发中很多 Render Props 已被 Custom Hooks 取代,但在某些需要动态控制 UI 渲染结构的场景中,它依然非常强大。

示例场景:水流/滚动监听、鼠标位置追踪

jsx 复制代码
import React, { useState } from 'react'

// 负责逻辑和状态的组件
function MouseTracker({ render }) {
  const [position, setPosition] = useState({ x: 0, y: 0 })

  const handleMouseMove = event => {
    setPosition({ x: event.clientX, y: event.clientY })
  }

  return (
    <div
      style={{ height: '200px', border: '1px solid' }}
      onMouseMove={handleMouseMove}
    >
      {/* 关键:把状态传给 render 函数 */}
      {render(position)}
    </div>
  )
}

// 使用它
function App() {
  return (
    <MouseTracker
      render={({ x, y }) => (
        <h1>
          当前鼠标位置:X: {x}, Y: {y}
        </h1>
      )}
    />
  )
}

5. 现代逻辑组合:Custom Hooks

在 React 16.8 之后,Hooks 成了纯逻辑组合(Non-visual logic composition)的终极方案。它解决了过去高阶组件(HOC)和 Render Props 导致的"嵌套地狱(Wrapper Hell)"问题。

如果你的组合目的仅仅是复用状态和逻辑,而不是复用 UI 结构,请毫不犹豫地选择 Custom Hooks。

jsx 复制代码
// 抽象出的计数器逻辑
function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue)
  const increment = () => setCount(c => c + 1)
  return { count, increment }
}

// 组件 A 使用该逻辑
function CounterComponent() {
  const { count, increment } = useCounter(10)
  return <button onClick={increment}>Count: {count}</button>
}

💡 总结与选型指南

在实际开发中,你可以根据以下逻辑来选择最适合的组合模式:

需求场景 推荐模式 核心思路
基础包装(如给一组组件加个通用的外框/边框) props.children 最简单的上下嵌套
复杂页面结构布局(如固定头部、侧边栏、主内容区) 插槽模式(Props Injection) 通过命名 props 传入组件
组件群状态共享(如 Tabs、Dropdown、Accordion) 复合组件(Compound Components) 外层用 Context,内层语义化组装
纯逻辑复用(如请求数据、监听网络、表单验证) Custom Hooks 剥离 UI,只组合和共享逻辑状态
相关推荐
CoCo的编程之路2 小时前
像素级突围:如何利用智能前端开发助手最大化提升页面构建速度?
前端·人工智能·ai编程·智能编程助手·文心快码baiducomate
techdashen2 小时前
npm 生态遭遇供应链攻击:color 包被投毒,每周 3200 万次下载全部受影响
前端·npm·node.js
UXbot2 小时前
轻量级原型工具如何支持Web应用的完整设计到开发链路
android·前端·人工智能·ios·交互·ui设计
边界条件╝2 小时前
前端构建引擎:从模块解析到产物生成
前端·javascript·vue.js·react.js
Setsuna_F_Seiei2 小时前
AI 提效之 Skills - Agent 的扩展技能教程
前端·javascript·ai编程
hhzz2 小时前
从混乱 HTML 到干净表格:用智能采集 API 啃下非规范电商页面
前端·html·网络爬虫
SuperEugene2 小时前
前端权限架构设计:路由/菜单/按钮/数据 四级权限体系|权限与菜单架构篇
前端·架构
LaughingZhu2 小时前
Product Hunt 每日热榜 | 2026-05-27
前端·人工智能·经验分享·html
ZC跨境爬虫11 小时前
跟着 MDN 学CSS day_16:(深入掌握背景与边框的艺术)
前端·css·ui·html·tensorflow