可扩展的 React 组件架构
复合组件、设计令牌与团队真正会采用的 tree-shakeable 组件库。
当 React 应用从原型成长为生产系统时,组件架构决定了代码库是优雅扩展还是在自身复杂度下崩溃。复合组件、设计令牌和可摇树优化的库构成了现代 UI 系统工具包,用于构建灵活、高性能且可维护的界面。本指南为 2026 年交付生产 React 应用的团队探索每种模式及实用指导。
复合组件的理由
复合组件是一种模式:父组件管理共享状态和上下文,子组件处理特定渲染职责。用户通过嵌套命名子组件而非通过扁平 prop 接口配置一切来组合 UI。这种模式贯穿成熟设计系统 — 标签页、手风琴、菜单和模态框都受益于复合组合。
主要优势是 API 人体工程学。将单体 prop 驱动组件与复合替代方案对比:
// Prop-driven — becomes unwieldy as features grow
<Tabs
items={[
{ label: "Overview", content: <Overview /> },
{ label: "Settings", content: <Settings />, disabled: true },
]}
defaultIndex={0}
onChange={handleTabChange}
variant="underline"
/>
// Compound — flexible, readable, extensible
<Tabs defaultValue="overview" onValueChange={handleTabChange}>
<Tabs.List>
<Tabs.Trigger value="overview">Overview</Tabs.Trigger>
<Tabs.Trigger value="settings" disabled>Settings</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="overview">
<Overview />
</Tabs.Content>
<Tabs.Content value="settings">
<Settings />
</Tabs.Content>
</Tabs>
复合组件通过 React Context 通信。父组件创建带有共享状态 — 活动标签、打开状态、选中值 — 的 context provider,子组件消费该 context 渲染其 UI 部分。这种解耦意味着你可以添加新子组件而无需修改父 API。
实现复合组件
首先定义带类型化接口的 context。根组件用 provider 包裹子组件并接受配置 props。子组件从 context 读取并根据共享状态条件渲染。
- 将子组件作为静态属性导出到根组件 —
Tabs.List、Tabs.Trigger。 - 在开发环境中验证组合,当子组件在父组件外使用时提供有用错误信息。
- 用 useMemo 保持 context 值稳定,防止所有子组件不必要重渲染。
- 同时支持受控和非受控模式以获得最大灵活性。
设计令牌作为单一事实来源
设计令牌是存储视觉设计决策 — 颜色、间距、排版、阴影、圆角 — 的命名实体,以平台无关的值表示。不要在组件中硬编码 #3b82f6 或 16px,而是引用语义令牌如 color.action.primary 或 spacing.component.md。
这种间接在大规模时带来切实收益:
- 一致性 — 每个组件引用相同令牌值,消除视觉漂移。
- 主题化 — 交换令牌值以在浅色、深色和高对比度主题间切换。
- 跨平台对齐 — 从单一来源导出令牌到 CSS、JavaScript、iOS 和 Android。
- 设计开发协作 — 设计师和开发者共享共同词汇。
// tokens.ts — semantic design tokens
export const tokens = {
color: {
surface: {
primary: "var(--color-surface-primary)",
secondary: "var(--color-surface-secondary)",
},
text: {
primary: "var(--color-text-primary)",
muted: "var(--color-text-muted)",
},
action: {
primary: "var(--color-action-primary)",
primaryHover: "var(--color-action-primary-hover)",
},
},
spacing: {
xs: "0.25rem",
sm: "0.5rem",
md: "1rem",
lg: "1.5rem",
xl: "2rem",
},
radius: {
sm: "0.25rem",
md: "0.5rem",
lg: "0.75rem",
full: "9999px",
},
} as const;
在全局样式表中定义 CSS 自定义属性,在组件中通过令牌对象引用。当设计团队更新品牌调色板时,你在一处更改值,每个组件自动更新。
摇树优化与模块化库
包体积直接影响应用性能,尤其在移动网络上。摇树优化在构建过程中消除死代码,但仅当你的库架构支持时才有效。重新导出每个组件的单体导出会迫使打包器包含整个库,即使你只使用一个按钮。
为最佳摇树优化构建组件库结构:
- 从各自模块入口点导出每个组件。
- 在
package.json中将包标记为无副作用。 - 避免从单一 index 重新导出一切的 barrel 文件。
- 对现代打包器使用 ESM 格式;仅在需要遗留支持时提供 CJS。
- 保持 peer 依赖外部 — React、React DOM 和样式库不应被打包。
// package.json — enable tree-shaking
{
"name": "@acme/ui",
"sideEffects": false,
"exports": {
"./button": "./dist/button/index.js",
"./input": "./dist/input/index.js",
"./modal": "./dist/modal/index.js",
"./tokens": "./dist/tokens/index.js"
}
}
// Consumer imports only what they need
import { Button } from "@acme/ui/button";
import { tokens } from "@acme/ui/tokens";
用包分析工具测量影响。导入单个组件并验证生产包排除无关模块。若整个库出现在输出中,调查 barrel 导出和副作用声明。
文件夹结构与就近放置
组织结构影响开发者查找和修改组件的速度。就近放置原则 — 将相关文件放在一起 — 减少上下文切换,使组件成为可独立移动、测试和删除的自包含单元。
组件库的可扩展文件夹结构:
src/components/Button/Button.tsx— 组件实现。src/components/Button/Button.test.tsx— 与组件就近的单元测试。src/components/Button/Button.stories.tsx— Storybook 文档。src/components/Button/index.ts— 公共导出面。src/tokens/— 共享设计令牌定义。src/hooks/— 跨多组件使用的共享 hooks。
避免按类型组织(每个特性的顶层 components/、hooks/、utils/)。构建应用级架构时按功能域分组,构建共享库时按组件分组。
组合优于配置
连接复合组件、设计令牌和可摇树优化模块的主线是哲学:偏好组合而非配置。组件应小而专注、可组合,而非带有数十个控制每种视觉变体的 props 的单体小部件。
应用开闭原则 — 组件对通过组合的扩展开放,对修改封闭。不要给按钮添加 showIcon prop,而是将图标作为子元素或插槽接受。不要提供五个枚举值的 size prop,而是暴露消费者可覆盖的 CSS 自定义属性。
这种方法产出经得起时间考验的组件库。新需求通过组合现有原语满足,而非修改久经考验的组件并 risking 整个系统的回归。
文档与采用
架构模式只有团队理解并一致采用时才交付价值。投资 Storybook 或类似文档平台,展示复合组件组合、令牌用法和导入模式。编写贡献指南,说明何时创建新组件 versus 扩展现有组件。
对重大结构选择运行架构决策记录 — 为何选择复合组件而非 render props,为何令牌放在 CSS 自定义属性而非 JavaScript 主题对象。未来团队成员将继承这些决策,需要上下文以保持一致性。
可扩展的 React 组件架构不是同时应用每种模式。而是为你的团队规模、应用复杂度和性能约束选择正确的抽象。复合组件处理灵活 UI 组合,设计令牌强制视觉一致性,可摇树优化模块保持包精简 — 三者共同构成支持从十个组件增长到一万个的基础。