7.5 样式复用
> **本节目标**:掌握 RML 的样式复用策略——全局样式、组件样式、工具类、CSS 模块。
7.5.1 样式复用的层次
┌─────────────────────────────────────────┐
│ 样式复用层次 │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ 全局样式 │ │ 组件样式 │ │
│ │ reset.css │ │ button.css │ │
│ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ 工具类 │ │ CSS 模块 │ │
│ │ flex, p-4 │ │ .button │ │
│ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────┘
7.5.2 全局样式
重置样式
/* src/styles/reset.css */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size: 14px;
line-height: 1.5;
color: #333;
background: #fff;
}
ul, ol {
list-style: none;
}
a {
color: inherit;
text-decoration: none;
}
button {
background: none;
border: none;
cursor: pointer;
font-family: inherit;
}
input, textarea {
font-family: inherit;
font-size: inherit;
}
全局变量
/* src/styles/variables.css */
:root {
/* 颜色 */
--color-primary: #007bff;
--color-secondary: #6c757d;
--color-success: #28a745;
--color-danger: #dc3545;
--color-warning: #ffc107;
--color-info: #17a2b8;
/* 文本 */
--color-text: #333;
--color-text-muted: #6c757d;
/* 背景 */
--color-bg: #fff;
--color-bg-alt: #f8f9fa;
/* 边框 */
--color-border: #dee2e6;
/* 间距 */
--spacing-unit: 8px;
/* 圆角 */
--border-radius: 4px;
/* 阴影 */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
在 build.rs 中加载
// build.rs
fn main() {
rml::compile_rml()
.with_style("styles/reset.css")
.with_style("styles/variables.css")
.with_style("styles/utilities.css")
.with_style("styles/components.css")
.compile();
}
7.5.3 组件样式
命名约定
使用 BEM(Block Element Modifier)命名法避免冲突:
/* src/styles/components/button.css */
/* Block */
.button {
display: inline-flex;
align-items: center;
justify-content: center;
padding: var(--spacing-unit) calc(var(--spacing-unit) * 2);
border: 1px solid transparent;
border-radius: var(--border-radius);
font-size: 14px;
cursor: pointer;
transition: background-color 0.2s;
}
/* Modifier */
.button--primary {
background: var(--color-primary);
color: white;
}
.button--secondary {
background: var(--color-secondary);
color: white;
}
.button--danger {
background: var(--color-danger);
color: white;
}
.button--outline {
background: transparent;
border-color: var(--color-border);
color: var(--color-text);
}
/* Size modifiers */
.button--sm {
padding: calc(var(--spacing-unit) / 2) var(--spacing-unit);
font-size: 12px;
}
.button--lg {
padding: calc(var(--spacing-unit) * 1.5) calc(var(--spacing-unit) * 3);
font-size: 16px;
}
/* State */
.button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
使用组件样式
<button class="button button--primary button--lg">主要按钮</button>
<button class="button button--secondary">次要按钮</button>
<button class="button button--danger button--sm">删除</button>
<button class="button button--outline" disabled>禁用</button>
7.5.4 工具类
工具类是单一用途的 CSS 类,提供常用样式:
布局工具类
/* src/styles/utilities.css */
/* Display */
.flex { display: flex; }
.inline-flex { display: inline-flex; }
.block { display: block; }
.inline-block { display: inline-block; }
.hidden { display: none; }
/* Flex direction */
.flex-row { flex-direction: row; }
.flex-col { flex-direction: column; }
.flex-row-reverse { flex-direction: row-reverse; }
.flex-col-reverse { flex-direction: column-reverse; }
/* Justify content */
.justify-start { justify-content: flex-start; }
.justify-end { justify-content: flex-end; }
.justify-center { justify-content: center; }
.justify-between { justify-content: space-between; }
.justify-around { justify-content: space-around; }
/* Align items */
.items-start { align-items: flex-start; }
.items-end { align-items: flex-end; }
.items-center { align-items: center; }
.items-stretch { align-items: stretch; }
/* Flex wrap */
.flex-wrap { flex-wrap: wrap; }
.flex-nowrap { flex-wrap: nowrap; }
/* Flex */
.flex-1 { flex: 1; }
.flex-auto { flex: auto; }
.flex-none { flex: none; }
.flex-grow { flex-grow: 1; }
.flex-shrink-0 { flex-shrink: 0; }
间距工具类
/* Padding */
.p-0 { padding: 0; }
.p-1 { padding: 4px; }
.p-2 { padding: 8px; }
.p-3 { padding: 12px; }
.p-4 { padding: 16px; }
.p-6 { padding: 24px; }
.p-8 { padding: 32px; }
.px-0 { padding-left: 0; padding-right: 0; }
.px-2 { padding-left: 8px; padding-right: 8px; }
.px-4 { padding-left: 16px; padding-right: 16px; }
.py-0 { padding-top: 0; padding-bottom: 0; }
.py-2 { padding-top: 8px; padding-bottom: 8px; }
.py-4 { padding-top: 16px; padding-bottom: 16px; }
/* Margin */
.m-0 { margin: 0; }
.m-2 { margin: 8px; }
.m-4 { margin: 16px; }
.m-auto { margin: auto; }
.mx-auto { margin-left: auto; margin-right: auto; }
.my-4 { margin-top: 16px; margin-bottom: 16px; }
/* Gap */
.gap-0 { gap: 0; }
.gap-1 { gap: 4px; }
.gap-2 { gap: 8px; }
.gap-4 { gap: 16px; }
.gap-6 { gap: 24px; }
文本工具类
/* Text align */
.text-left { text-align: left; }
.text-center { text-align: center; }
.text-right { text-align: right; }
/* Font size */
.text-xs { font-size: 12px; }
.text-sm { font-size: 14px; }
.text-base { font-size: 16px; }
.text-lg { font-size: 18px; }
.text-xl { font-size: 20px; }
.text-2xl { font-size: 24px; }
/* Font weight */
.font-normal { font-weight: normal; }
.font-medium { font-weight: 500; }
.font-bold { font-weight: bold; }
/* Color */
.text-primary { color: var(--color-primary); }
.text-danger { color: var(--color-danger); }
.text-success { color: var(--color-success); }
.text-muted { color: var(--color-text-muted); }
/* Text decoration */
.no-underline { text-decoration: none; }
.underline { text-decoration: underline; }
.line-through { text-decoration: line-through; }
背景工具类
.bg-primary { background: var(--color-primary); }
.bg-secondary { background: var(--color-secondary); }
.bg-success { background: var(--color-success); }
.bg-danger { background: var(--color-danger); }
.bg-white { background: white; }
.bg-transparent { background: transparent; }
边框工具类
.border { border: 1px solid var(--color-border); }
.border-0 { border: none; }
.border-top { border-top: 1px solid var(--color-border); }
.border-bottom { border-bottom: 1px solid var(--color-border); }
.rounded { border-radius: var(--border-radius); }
.rounded-lg { border-radius: 8px; }
.rounded-full { border-radius: 9999px; }
使用工具类
<div class="flex items-center justify-between p-4 bg-white rounded shadow">
<h1 class="text-xl font-bold text-primary">标题</h1>
<button class="button button--primary">按钮</button>
</div>
7.5.5 CSS 模块
CSS 模块提供作用域隔离,避免样式冲突:
定义 CSS 模块
/* src/components/button/button.module.css */
.button {
display: inline-flex;
align-items: center;
padding: 8px 16px;
border: 1px solid transparent;
border-radius: 4px;
cursor: pointer;
}
.primary {
background: #007bff;
color: white;
}
.large {
padding: 12px 24px;
font-size: 16px;
}
在组件中使用
// src/components/button/button.rml.rs
use rml::prelude::*;
#[component]
pub struct Button {
pub text: SharedString,
pub variant: SharedString,
pub size: SharedString,
}
<!-- src/components/button/button.rml -->
<button class="button {variant} {size}">
{text}
</button>
在父视图中使用
<div>
<Button text="提交" variant="primary" size="large" />
<Button text="取消" variant="secondary" size="default" />
</div>
7.5.6 样式复用的策略
策略一:工具类优先
<!-- ✅ 工具类组合 -->
<div class="flex items-center gap-4 p-4 bg-white rounded shadow">
<img src={avatar} class="w-12 h-12 rounded-full" />
<div class="flex-1">
<h3 class="text-lg font-bold">{name}</h3>
<p class="text-sm text-muted">{email}</p>
</div>
</div>
<!-- ❌ 自定义类名 -->
<div class="user-card">
<img src={avatar} class="user-avatar" />
<div class="user-info">
<h3 class="user-name">{name}</h3>
<p class="user-email">{email}</p>
</div>
</div>
策略二:组件样式用于复杂组件
/* 复杂组件用 BEM 命名 */
.user-card {
display: flex;
align-items: center;
gap: 16px;
padding: 16px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.user-card__avatar {
width: 48px;
height: 48px;
border-radius: 50%;
}
.user-card__name {
font-size: 18px;
font-weight: bold;
}
.user-card__email {
font-size: 14px;
color: #6c757d;
}
.user-card--highlighted {
border: 2px solid #007bff;
}
策略三:CSS 变量实现主题化
/* 用 CSS 变量定义主题 */
:root {
--user-card-bg: white;
--user-card-text: #333;
--user-card-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.user-card {
background: var(--user-card-bg);
color: var(--user-card-text);
box-shadow: var(--user-card-shadow);
}
7.5.7 样式复用的最佳实践
1. DRY 原则
/* ✅ 提取公共样式 */
.button-base {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 8px 16px;
border: 1px solid transparent;
border-radius: 4px;
cursor: pointer;
}
.button-primary {
/* 继承 button-base 的样式 */
background: #007bff;
color: white;
}
/* ❌ 重复样式 */
.button-primary {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 8px 16px;
border: 1px solid transparent;
border-radius: 4px;
cursor: pointer;
background: #007bff;
color: white;
}
2. 单一职责
/* ✅ 单一职责 */
.text-center { text-align: center; }
.text-red { color: red; }
.p-4 { padding: 16px; }
/* ❌ 混合职责 */
.centered-red-box {
text-align: center;
color: red;
padding: 16px;
}
3. 命名清晰
/* ✅ 清晰命名 */
.button-primary { ... }
.card-header { ... }
.text-muted { ... }
/* ❌ 模糊命名 */
.style1 { ... }
.div1 { ... }
.red { ... }
4. 避免过度嵌套
/* ✅ 扁平结构 */
.card { ... }
.card-header { ... }
.card-body { ... }
/* ❌ 过度嵌套 */
.card .header .title .text { ... }
7.5.8 完整示例:样式系统组织
src/
├── styles/
│ ├── reset.css # 重置样式
│ ├── variables.css # CSS 变量
│ ├── utilities.css # 工具类
│ ├── components/
│ │ ├── button.css # 按钮样式
│ │ ├── card.css # 卡片样式
│ │ ├── form.css # 表单样式
│ │ ├── table.css # 表格样式
│ │ └── modal.css # 模态框样式
│ └── themes/
│ ├── light.css # 亮色主题
│ └── dark.css # 暗色主题
├── components/
│ ├── button/
│ │ ├── button.rml
│ │ └── button.rml.rs
│ └── card/
│ ├── card.rml
│ └── card.rml.rs
└── views/
├── home/
│ ├── home.rml
│ └── home.rml.rs
└── user/
├── user.rml
└── user.rml.rs
// build.rs
fn main() {
rml::compile_rml()
// 基础样式
.with_style("styles/reset.css")
.with_style("styles/variables.css")
// 主题
.with_style("styles/themes/light.css")
.with_style("styles/themes/dark.css")
// 工具类
.with_style("styles/utilities.css")
// 组件样式
.with_style("styles/components/button.css")
.with_style("styles/components/card.css")
.with_style("styles/components/form.css")
.with_style("styles/components/table.css")
.with_style("styles/components/modal.css")
// 编译
.compile();
}
7.5.9 小结
RML 的样式复用策略:
- **全局样式**:重置样式、全局变量 - **组件样式**:BEM 命名,避免冲突 - **工具类**:单一职责,灵活组合 - **CSS 模块**:作用域隔离 - **CSS 变量**:主题化
最佳实践:
- 工具类优先,组件样式用于复杂组件 - DRY 原则,提取公共样式 - 单一职责,清晰命名 - 避免过度嵌套
下一章 → [第 8 章 · 生命周期管理](../08-lifecycle/INDEX.md)