Rebass 实战指南:基于 Styled System 的 React 原始 UI 组件库
Rebass 实战指南:基于 Styled System 的 React 原始 UI 组件库
导读
Rebass 是一套用 React 构建的"原始(primitive)"UI 组件库,底层由 Styled System 驱动,目标是以极小的学习成本帮助团队快速起步搭建设计系统。本文以仓库根目录的 README.md 为主线,结合 packages/ 下各组件的源码与测试用例,系统讲解 Rebass 的安装、核心组件、设计原则、主题化(Theming)与扩展机制。读完本文,你将掌握如何用 Box、Flex、Heading、Button 等基础组件组合出真实业务界面,并理解其数组式响应式语法、sx 属性与 variant 变体机制的底层原理。
Rebass 是什么
Rebass 的定位在项目描述中写得很清楚:React primitive UI components built with Styled System。也就是说,它提供的不是开箱即用的"成品 UI 库",而是一组语义最简、样式无强观点的基础组件,让开发者可以在其上自由组合、按设计规范定制,从而"不用把整个海洋煮沸(Start your design system without boiling the ocean)"。
仓库采用 Lerna + Yarn workspaces 管理多包结构,packages/ 下包含了:
rebass——核心组件包(Text、Heading、Button、Link、Image、Card 等),当前版本 4.0.7(见 packages/rebass/package.json);reflexbox——布局基座(Box、Flex),Rebass 所有组件都构建在它之上;preset/preset-material——默认与 Material 风格的主题预设;forms——Label、Input、Select、Textarea、Radio、Checkbox、Switch、Slider等表单组件;layout——Tiles等栅格/布局组件;space——不产生包裹 DOM 的间距组件;docs——官网文档站(Gatsby + MDX);bundler——用于构建发布产物的打包工具。
安装与快速上手
核心包的安装非常简单:
npm i rebass
(仓库为多包 workspace,安装各子包同理,如 npm i @rebass/forms、npm i @rebass/preset。)
安装后即可直接使用,README 给出的最小示例是一个由 Box、Heading、Button 组成的问候卡片:
import React from 'react'
import { Box, Heading, Button } from 'rebass'
export default props =>
<Box>
<Heading>Hello</Heading>
<Button>Rebass</Button>
</Box>
这里体现了两条核心体验:组件是极简的原始块(Box 默认渲染 div),样式通过属性直接声明(Styled System props,如 p、m、width、color、bg 等)。
响应式数组语法
Rebass 支持 Styled System 的移动端优先数组语法:给样式属性传数组,数组的每一项对应一个断点(默认 theme.breakpoints),实现零媒体查询的响应式布局:
// 最小屏宽 100%,下一断点 50%,再下一断点 25%
<Box width={[ '100%', '50%', '25%' ]} />
这种语法同样适用于 p、m、fontSize 等所有样式属性与 sx 属性。断点宽度可通过主题中的 theme.breakpoints 数组自定义,其默认约定可参考 reflexbox 的 README。
核心组件:从 Box 到 Card
Rebass 的六个核心组件都定义在 packages/rebass/src/index.js 中,它们的共同特征是:全部是 Box 的扩展,并统一使用 forwardRef 转发 ref。组件结构可用下面这张关系图概括:
Box / Flex(来自 reflexbox)
└── Text(tx='text',渲染 div)
└── Heading(as='h2',tx='text',variant='heading')
└── Link(as='a',variant='link')
└── Button(as='button',tx='buttons',variant='primary')
└── Image(as='img')
└── Card(variant='card')
下面逐一展开各组件的行为,均以源码与 packages/rebass/test/index.js 中的测试断言为依据。
Box 与 Flex(来自 Reflexbox)
Box 与 Flex 由 packages/reflexbox/src/index.js 实现:
export const Box = styled('div', {
shouldForwardProp
})({
boxSizing: 'border-box',
margin: 0,
minWidth: 0,
},
base,
variant,
sx,
props => props.css,
compose(
space,
layout,
typography,
color,
flexbox,
),
)
export const Flex = styled(Box)({
display: 'flex'
})
Box默认渲染div,但通过as属性可渲染任意元素(测试用例<Box as='header' />断言渲染为header);- 内置
box-sizing: border-box; margin: 0; min-width: 0,从源头规避 Flexbox 布局中的经典坑; - 通过
compose组合了 Styled System 的space、layout、typography、color、flexbox五组样式能力; Flex只是给Box加上display: flex,其余能力完全继承。
Text
export const Text = forwardRef((props, ref) =>
<Box ref={ref} tx='text' {...props} />
)
Text 是纯文本容器,声明了 tx='text',意味着它的 variant 会从主题的 text 键中查找。测试用例展示了它的用法:<Text textAlign='center' fontWeight='bold' fontStyle='italic' /> 会分别生成 text-align、font-weight、font-style 规则;配合主题 text.caps 变体可实现全大写排版。
Heading
export const Heading = forwardRef((props, ref) =>
<Box
ref={ref}
as='h2'
tx='text'
variant='heading'
{...props}
__css={{
fontSize: 4,
fontFamily: 'heading',
fontWeight: 'heading',
lineHeight: 'heading',
}}
/>
)
Heading 默认渲染为 h2,并通过 __css 声明默认排版:fontSize: 4(对应预设 fontSizes 的第 5 项,即 24px,见测试断言)、字体系列/字重/行高均引用主题键 heading。使用 as 可以切换为 h1、h3 等其他标题级别。
Button
export const Button = forwardRef((props, ref) =>
<Box
ref={ref}
as='button'
tx='buttons'
variant='primary'
{...props}
__css={{
appearance: 'none',
display: 'inline-block',
textAlign: 'center',
lineHeight: 'inherit',
textDecoration: 'none',
fontSize: 'inherit',
px: 3,
py: 2,
color: 'white',
bg: 'primary',
border: 0,
borderRadius: 4,
}}
/>
)
Button 默认渲染为原生 button 元素,tx='buttons' + variant='primary' 表示默认样式来自主题的 buttons.primary;__css 提供了按钮必备的基线样式(重置 appearance、去掉边框、主题色背景等)。测试还验证了 <Button as='a' /> 可渲染为链接,方便实现"按钮外观、链接行为"的场景。
Link、Image、Card
- Link:
as='a'+variant='link',默认样式从主题variants.link读取(预设中为color: 'primary'); - Image:
as='img',__css内置maxWidth: '100%'与height: auto,保证图片响应式且不变形; - Card:默认
variant='card',样式完全交给主题variants.card定义;测试用例展示了通过p、bg属性与sx属性组合出带圆角、投影的卡片。
特性解读(Features)
README 对 Rebass 的特性概括为以下八点,我们结合仓库逐一展开:
| 特性 | 含义与仓库依据 |
|---|---|
| 快速起步,不用"煮沸整个海洋" | 只提供原始组件,不捆绑业务样式;Getting Started 文档(packages/docs/src/pages/getting-started.mdx)演示了用 Box/Card/Image/Heading/Text 五分钟拼出产品卡片 |
| 设计约束与用户自定义尺度 | 所有样式值走主题 scale(space、fontSizes、colors 等),禁止随意像素值散落各处;见 preset 主题 |
| Styled System props 带来一流开发体验 | 空格、布局、排版、颜色、Flexbox 五组属性全部可用(reflexbox 源码) |
| 主题化一等公民,完全兼容 Theme UI | 遵循 [system-ui theme specification] 生态,ThemeProvider 可互换使用(详见下文"主题化"一节) |
| 数组语法实现移动端优先响应式 | 见上文"响应式数组语法" |
| Box + Flex 提供 Flexbox 布局 | 布局基座来自 reflexbox |
| 高设计与开发速度的内置灵活性 | as 换元素、variant 换风格、sx 写局部样式,组合自由度极高 |
| 体积约 4KB | README 声明的最小体积,配合多包按需安装进一步控制首屏负担 |
设计原则:Minimal · Useful · Unopinionated
README 明确了 Rebass 的七个设计原则——Minimal(最小)、Useful(有用)、Unopinionated(无观点)、Flexible(灵活)、Consistent(一致)、Extensible(可扩展)、Themeable(可主题化),并引用 Unix 哲学"做一件事,并把它做好"。
这套原则在源码中处处可见:
- 最小:核心组件源码总共不足百行,每个组件只做"元素 + 默认变体 + 基线样式"三件事;
- 无观点:
Heading不强制h1,Button不绑定主题外的特殊样式,默认值全部可被 props 覆盖; - 可扩展:组件全部由
Box派生且支持forwardRef,意味着你可以const Card = props => <Box variant='card' {...props} />式地自由组合(官方也提供了专门的 扩展指南文档); - 可主题化:所有可变量均通过主题键(
text、buttons、variants)寻址,而不是写死在组件里。
主题化(Theming)
ThemeProvider 的使用
Rebass 组件默认不带任何主题样式,需要在应用根部用 ThemeProvider 注入主题。官方推荐的组合是 @rebass/preset 预设主题 + emotion-theming 的 ThemeProvider:
npm i @rebass/preset emotion-theming
import React from 'react'
import { ThemeProvider } from 'emotion-theming'
import theme from '@rebass/preset'
export default props =>
<ThemeProvider theme={theme}>
{props.children}
</ThemeProvider>
如果使用 Theme UI 生态,也可以换成 theme-ui 的 ThemeProvider 或 gatsby-plugin-theme-ui。
默认预设主题的结构
@rebass/preset 源码 展示了完整的主题结构,它是理解 Rebass 一切样式行为的起点:
export const preset = {
colors: {
text: '#000',
background: '#fff',
primary: '#07c',
secondary: '#30c',
muted: '#f6f6f9',
gray: '#dddddf',
highlight: 'hsla(205, 100%, 40%, 0.125)',
},
fonts: {
body: 'system-ui, sans-serif',
heading: 'inherit',
monospace: 'Menlo, monospace',
},
fontSizes: [12, 14, 16, 20, 24, 32, 48, 64, 96],
fontWeights: { body: 400, heading: 700, bold: 700 },
lineHeights: { body: 1.5, heading: 1.25 },
space: [0, 4, 8, 16, 32, 64, 128, 256, 512],
sizes: { avatar: 48 },
radii: { default: 4, circle: 99999 },
shadows: { card: '0 0 4px rgba(0, 0, 0, .125)' },
text: { heading: {…}, display: {…}, caps: {…} },
variants: { avatar: {…}, card: {…}, link: {…}, nav: {…} },
buttons: { primary: {…}, outline: {…}, secondary: {…} },
styles: { root: {…} },
}
关键点:
space数组决定p={3}= 16px、m={2}= 8px 等取值;fontSizes数组决定fontSize={4}= 24px(Heading 默认值);buttons.primary决定Button默认外观;variants.card决定Card默认外观,text.caps等决定 Text 变体。
仓库还提供了另一套 Material 风格预设(@rebass/preset-material,标注为 work-in-progress),使用 Roboto 字体体系与 Material Design 色板。
自定义主题与 Variants
遵循 Theme Specification] 的主题都可以直接使用,例如在 [入门文档 中演示的自定义主题——定义自己的 fontSizes、colors 与 buttons 变体:
const theme = {
fontSizes: [12, 14, 16, 24, 32, 48, 64],
colors: {
primary: '#07c',
gray: '#f6f6ff',
},
buttons: {
primary: { color: 'white', bg: 'primary' },
outline: {
color: 'primary',
bg: 'transparent',
boxShadow: 'inset 0 0 0 2px'
},
},
}
然后通过 variant 属性切换:
<Button variant='primary' mr={2}>Beep</Button>
<Button variant='secondary'>Boop</Button>
variant 机制的底层实现在 reflexbox 源码 中:组件通过 get(theme, tx + '.' + variant, get(theme, variant)) 依次在 tx 指定的主题键(如 buttons、text)与全局 variants 键中查找样式对象;sx 属性则通过 css(props.sx)(props.theme) 编译为样式。这意味着:变体是主题驱动、可继承的(如 preset 中 buttons.outline 通过 variant: 'buttons.primary' 继承 primary 的基线样式再覆盖颜色)。
sx 与 css 属性的分工
sx:主题感知的样式属性,支持引用主题 scale 的值(color: 'primary'、p: 4等),适合写局部设计;css:不透传主题转换的原始样式属性,适合需要"退出主题系统"的场合(reflexbox 的 README 明确说明了两者的分工)。
布局与响应式:Reflexbox 与 Tiles
Rebass 的布局能力全部来自 reflexbox 的 Box/Flex。reflexbox 自称"the original Box component since 2015",是原 Reflexbox、Grid Styled 与 Rebass Grid 三库 API 整合的产物(见 packages/reflexbox/README.md)。
一个典型的两栏响应式布局:
import React from 'react'
import { Flex, Box } from 'rebass' // 或从 'reflexbox' 引入
export default props =>
<Flex flexWrap='wrap'>
<Box width={[ 1, 1/2 ]} p={3}>Reflex</Box>
<Box width={[ 1, 1/2 ]} p={3}>Box</Box>
</Flex>
Reflexbox 支持五组 Styled System props,完整映射表见 reflexbox README,这里给出常用摘录:
| 分组 | 属性示例 | 主题键 |
|---|---|---|
| Space | m mt mr mb ml mx my / p pt pr pb pl px py |
space |
| Layout | width height minWidth maxWidth minHeight maxHeight |
sizes |
| Typography | fontFamily fontSize fontWeight lineHeight letterSpacing |
fonts fontSizes fontWeights lineHeights letterSpacings |
| Color | color bg opacity |
colors |
| Flexbox | alignItems justifyContent flexWrap flexDirection flex order 等 |
N/A(直接映射 CSS) |
使用 Styled Components 的团队,可改从 reflexbox/styled-components 导入同一套 API(reflexbox 通过 NODE_ENV=styled 的构建脚本生成 Styled Components 版本,见 packages/reflexbox/package.json)。
此外,@rebass/layout 提供了 Tiles 组件,基于 CSS Grid 实现自适应栅格:传入 width 自动生成 repeat(auto-fit, minmax(...)),传入 columns 生成 repeat(n, 1fr),并支持响应式数组值。
扩展组件包
表单组件(@rebass/forms)
@rebass/forms 提供了可访问(accessible)且可主题化的表单组件:Label、Input、Select、Textarea、Radio、Checkbox、Switch、Slider。它们同样构建在 Box/Flex 之上,通过 tx='forms' + variant 接入主题。一个完整表单示例见 packages/forms/README.md,涵盖 Label htmlFor 关联、Radio/Checkbox 嵌套在 Label 中、以及用 Flex + mx={-2} 实现负 margin 栅格间距等技巧。
无包裹间距组件(@rebass/space)
@rebass/space 解决"只想给子元素加间距、不想多一层 div"的问题。它通过 StyledChildren 将样式类名直接克隆到每个子元素上,自身不渲染 DOM:
import Space from '@rebass/space'
const App = props => (
<Space mx={3} my={[ 2, 3 ]}>
<h1>Hello</h1>
<h2>Hi</h2>
<button>Beep</button>
</Space>
)
支持的属性与 space 工具一致(m/mt/mr/mb/ml/mx/my 及对应 p 系列,取值可为数字、字符串或数组),完整表格见 packages/space/README.md。
工程质量与测试保障
仓库对代码质量有严格要求,根目录 package.json 中 Jest 覆盖率阈值设置为 branches/functions/lines/statements 均为 90%。测试主要位于 packages/*/test/ 下:
- packages/rebass/test/index.js:验证每个核心组件的渲染元素、默认样式与主题变体行为;
- packages/reflexbox/test/index.js:验证
Box的as、五组 style props、sx/css属性、variant/tx机制,以及 style props 不会泄漏到 DOM(shouldForwardProp过滤,测试断言最终className是唯一 props); - packages/space/test/index.js、packages/preset/test/index.js 等覆盖各自包行为。
本地运行测试的方式(仓库根目录):
yarn install
yarn test
开发文档站点:yarn start(Gatsby dev server);构建站点:yarn build。
从 v3 迁移与版本历史
README 提示从 v3 升级的用户参考官网 Migration Guide(仓库内对应文档为 packages/docs/src/pages/migrating.mdx)。同时 README 列出了历史版本入口(v3.2.2、v2.3.2、v1.0.7),便于查阅旧版行为。当前核心包版本为 4.0.7,仅依赖 reflexbox@^4.0.6,依赖面极窄(packages/rebass/package.json)。
相关生态与延伸阅读
Rebass 处于 Styled System 生态的中间层,README 将相关项目分为两组:
- 上游依赖:Styled System(样式 props 引擎);
- 平级协作:Theme UI(主题/风格化框架)、Emotion、Styled Components(底层样式引擎,Rebass 默认基于 Emotion,同时为 Styled Components 提供构建产物)。
仓库内的文档站点提供了更深入的主题化(theming.mdx)、Props 速查(props.mdx)、组件扩展(extending.mdx)以及 CSS Grid 布局等实践指南,均为可继续深入阅读的仓库内一手资料。
小结
Rebass 的核心价值在于"少而精":以 Styled System 的 props 体系为引擎,以 reflexbox 的 Box/Flex 为布局基座,以主题 scale + variant 变体为约束机制,再辅以数组式响应式语法,构成了一个可快速起步、可深度定制、体积精简(约 4KB)的原始组件库。本文梳理的源码、测试与文档路径,均可作为后续实践与二次开发的直接参照。
本文内容均以当前仓库 README.md 及各包源码、测试、配置文件为事实依据。