BEM 通过单类名强制绑定样式与结构,确保组件可复制、迁移和协作安全;后代选择器如 .card .title 因隐式依赖 DOM 层级易失效或冲突,而 BEM 类名如 .card__title、.button--primary 明确归属、无上下文依赖。

直接说结论:BEM 不是让组件“看起来能复用”,而是通过命名强制绑定样式与结构,让组件在复制、迁移、多人协作时,不依赖上下文也能正确渲染、安全修改。
为什么 .card .title 复用一次就出问题
这类后代选择器把样式作用域隐式绑在 DOM 层级上。一旦目标组件被嵌入到另一个已有 .card 的页面里,或者父容器 class 名微调(比如从 .section-card 改成 .feature-card),.card .title 就失效或误匹配。
更常见的是:两个不同团队写的 .button 同时加载,后引入的 CSS 覆盖前者的 :hover,但没人意识到——因为没显式声明“这个 button 样式只属于我这个模块”。
用 BEM 后,所有选择器都是单类名:.card__title、.button--primary。浏览器匹配快,开发者也一眼看出归属。
立即学习“前端免费学习笔记(深入)”;
怎么写才算真正可复用的 BEM 类名
关键不是字符长度,而是能否脱离当前 HTML 结构独立存在、被理解、被迁移。
-
.user-card__avatar✅ —— avatar 是 user-card 的视觉组成部分,挪到新页面仍能识别上下文 -
.user-card__avatar--round✅ —— 状态修饰符依附明确,不单独使用 -
.user-card__header__title❌ —— 元素不能嵌套;__header本身已是一个子结构,应拆为独立 block -
.is-active或.active❌ —— 纯状态类无上下文,不知道它属于谁;应写作.user-card--selected或.button--disabled -
.search-input✅ —— 功能命名优于位置命名(如.header__search),方便在弹窗、侧边栏等多处复用
现有项目补 BEM 容易踩的三个坑
别一上来就重命名全部 class,容易翻车。重点盯住高频、跨业务复用的模块(如表单控件、卡片、弹窗)。
常见错误现象:
- 只改子元素 class,漏掉块根:写了
.card__title却保留旧的.title,导致样式残留或冲突 - 修饰符挂错位置:给
.card__title加--large,而不是统一由.card--large控制整体缩放——这破坏了 modifier 应作用于 block 根的原则 - 和第三方组件库硬刚:直接覆盖
.el-button类名。结果升级 Ant Design 后样式崩,还污染全局。稳妥做法是封装 wrapper:<div class="my-button"><el-button class="my-button__inner">提交</el-button></div>,只对.my-button和.my-button__inner写样式
Sass 中写 BEM 别让嵌套反向破坏扁平性
Sass 的 & 嵌套本身不坏,但容易写出违反 BEM 原则的 CSS。编译后如果出现空格、> 或 +,就等于退回到传统选择器模式。
正确写法示例:
.card {
&__title { font-size: 1.2rem; }
&--featured { background: #f0f8ff; }
}
错误写法(会导致编译出 .card .card__title):
.card {
.card__title { ... } // 手动重复写 class,失去 BEM 意义
}
.card &__title { ... } // & 在这里指向父级,生成后代选择器
检查编译后 CSS 最简单的方法:全局搜索 (空格)、>、+,确认没有超出 BEM 单类名约定的组合器。
真正难的不是写对第一个 .button__icon,而是坚持不让某个临时需求打破 block 边界——比如把 .icon 塞进 .modal__close 而不抽成独立原子组件。那才是复用效率卡死的地方。


















