基类+修饰类
📅 2026/7/23 18:15:16
👁️ 阅读次数
📝 编程学习
“基类 + 修饰类”是 CSS 工程化中最经典、最优雅的命名与组织模式。它源自BEM(Block Element Modifier)方法论,但在 Vue 3 组件化开发中被发挥到了极致。
你可以把这种模式理解为“乐高积木设计法”:
- 基类(Base):定义一块积木的“通用骨架”(尺寸、字体、基础颜色)。
- 修饰类(Modifier):定义这块积木的“特定皮肤”或“特殊状态”(颜色变红、尺寸变大、是否禁用)。
这样做最大的好处是:永远不需要写重复的样式代码,一个类只干一件事。
一、命名规范(BEM 标准写法)
在 CSS 中,我们通常用-和--来区分:
| 类型 | 命名格式 | 示例 | 含义 |
|---|---|---|---|
| 基类(Block) | .block | .btn | 按钮的基础样式(宽高、内边距、边框圆角) |
| 修饰类(Modifier) | .block--modifier | .btn--primary.btn--large | 主题色变为主色 尺寸变大 |
| 状态类(State) | .is-状态或.block.is-状态 | .is-disabled.is-active | 临时状态(禁用、激活中),通常配合 JS 控制 |
二、原生 CSS 中的写法(如何组合)
假设我们要做一个按钮组件:
<!-- 1. 只写基类:默认灰色普通按钮 --> <button class="btn">默认按钮</button> <!-- 2. 基类 + 修饰类:变成蓝色大按钮 --> <button class="btn btn--primary btn--large">主要大按钮</button> <!-- 3. 基类 + 状态类:变成禁用状态 --> <button class="btn is-disabled" disabled>禁用按钮</button>对应的 CSS 代码(利用层叠覆盖):
/* 基类:定义所有按钮共有的骨架 */ .btn { display: inline-block; padding: 8px 16px; font-size: 14px; border-radius: 4px; border: 1px solid #dcdfe6; background: #fff; color: #606266; cursor: pointer; transition: all 0.3s; } /* 修饰类:只改颜色(基类里有的属性,覆盖掉即可) */ .btn--primary { background: #409eff; border-color: #409eff; color: #fff; } /* 修饰类:只改尺寸 */ .btn--large { padding: 12px 24px; font-size: 16px; } /* 状态类:只改交互反馈 */ .is-disabled { opacity: 0.6; cursor: not-allowed; pointer-events: none; /* 禁止点击事件 */ }三、🔥 Vue 3 中的最佳实践(动态绑定修饰类)
在 Vue 中,你极少会在模板里硬编码写死class="btn btn--primary"(因为这样就失去响应式意义了)。正确做法是利用:class动态绑定,将修饰类与组件的props或响应式状态关联起来。
1. 封装一个通用的BaseButton.vue组件
这是你在 Vite 项目中实际会写的代码:
<!-- src/components/BaseButton.vue --> <template> <button class="btn" :class="[ `btn--${type}`, // 动态主题(primary / success / warning) { 'is-disabled': disabled, [`btn--${size}`]: size // 动态尺寸(large / small) } ]" :disabled="disabled" > <slot /> </button> </template> <script setup lang="ts"> // 定义组件的 API,让父组件通过传参来控制样式 type ButtonType = 'default' | 'primary' | 'success' | 'warning'; type ButtonSize = 'default' | 'large' | 'small'; const props = withDefaults(defineProps<{ type?: ButtonType; size?: ButtonSize; disabled?: boolean; }>(), { type: 'default', size: 'default', disabled: false, }); </script> <style scoped> /* 基类 */ .btn { padding: 8px 16px; font-size: 14px; border-radius: 4px; border: 1px solid transparent; cursor: pointer; transition: all 0.3s; /* 默认样式(default) */ background: #fff; border-color: #dcdfe6; color: #606266; } .btn:hover:not(.is-disabled) { opacity: 0.8; } /* 修饰类:主题 */ .btn--primary { background: #409eff; border-color: #409eff; color: #fff; } .btn--success { background: #67c23a; border-color: #67c23a; color: #fff; } .btn--warning { background: #e6a23c; border-color: #e6a23c; color: #fff; } /* 修饰类:尺寸 */ .btn--large { padding: 12px 24px; font-size: 16px; } .btn--small { padding: 6px 12px; font-size: 12px; } /* 状态类 */ .is-disabled { opacity: 0.6; cursor: not-allowed; } </style>2. 父组件中使用(干净得像写 HTML 属性):
<template> <!-- 默认灰色 --> <BaseButton>提交</BaseButton> <!-- 主要蓝色,大号 --> <BaseButton type="primary" size="large">提交</BaseButton> <!-- 警告色,禁用 --> <BaseButton type="warning" disabled>已过期</BaseButton> </template>四、配合 CSS 预处理器(SCSS)的进阶写法
如果你在 Vite 中安装了sass(pnpm add -D sass),可以用 SCSS 的&父选择器语法,让修饰类嵌套在基类内部,逻辑更集中:
.btn { padding: 8px 16px; // ... // 使用 & 代表 .btn,生成 .btn--primary &--primary { background: #409eff; color: #fff; } &--large { padding: 12px 24px; } // 状态类也嵌套进去,生成 .btn.is-disabled &.is-disabled { opacity: 0.6; cursor: not-allowed; } }五、基类 + 修饰类 vs 原子化 CSS(Tailwind)
既然你是 Vite 项目,可能会听到“Tailwind CSS(原子类)”。这两种模式不冲突,而是互补:
| 模式 | 基类 + 修饰类(BEM) | 原子化 CSS(Tailwind) |
|---|---|---|
| 写法 | .btn--primary { background: blue; } | class="bg-blue-500" |
| 优点 | 逻辑清晰,语义强,适合组件库内部 | 开发极快,不用想类名,适合业务页面 |
| 缺点 | 需要频繁在 HTML 和 CSS 间切换 | 类名太长,可读性依赖工具提示 |
大厂最佳实践(Vue 3 生态):
- 组件内部(如 Element Plus / Naive UI):用基类+修饰类,提供语义化的
type/sizeProps。 - 业务页面(App.vue 或具体页面):用原子类(配合
unocss或tailwind)快速搭建布局。
给你一个无敌的“避坑原则”
- 永远不要在基类里写死
.btn { color: red; },把颜色留给修饰类。 - 状态类(
.is-*)不要单独使用,必须配合基类一起出现(class="btn is-disabled"),因为 CSS 权重靠组合提升。 - 修饰类只覆盖基类中已有的属性,如果基类没写
border-radius,修饰类里也不要写,除非你想新增特性。
现在你可以在你的 Vite 项目里试着封装一个BaseButton了,把之前学的transition、:hover、box-sizing都结合进去。封装完这个组件,你对 CSS 模块化的理解直接上一个台阶。
接下来想学transition过渡动画(让按钮渐变消失/出现),还是想攻克position定位(做吸顶导航、浮动客服按钮)?你来挑。
编程学习
技术分享
实战经验