bem 通过 block、element、modifier 三要素建立命名契约,需从文件结构(如 components/button/button.css)和类名规范(.button、.button__icon、.button--primary)落地,禁用隐式嵌套,允许伪类与媒体查询,迁移需分层处理,不替代 css modules 等封装方案。

为什么传统嵌套 CSS 容易失控
嵌套层级一深,.header .nav .item .link:hover 这种选择器就变成“猜样式游戏”:改一个 margin 可能影响三级外的弹窗,删个类名不敢确定有没有漏掉子组件。根源不是嵌套本身,而是缺乏命名契约——没人知道这个 .item 是全局通用项,还是仅属于当前导航模块。
BEM 的三要素怎么落地到文件结构里
BEM 要生效,得从目录和文件名就开始约束,否则光靠命名习惯撑不住团队协作。关键不是“用不用双下划线”,而是让每个类名自带上下文:
-
button.css里只写.button(Block)、.button__icon(Element)、.button--primary(Modifier),绝不出现.header__button - 组件级样式文件按模块拆分:
components/card/card.css对应.card、.card__title,不混入布局类或主题类 - 如果真需要跨模块组合(比如
.card在.dashboard里有特殊间距),用工具链注入:PostCSS 插件自动加前缀,而不是手写.dashboard .card
遇到继承/覆盖需求时 BEM 怎么避免冗余
很多人卡在“BEM 不让用嵌套,那 hover 状态、响应式断点怎么写?”——其实 BEM 不禁止伪类和媒体查询,只是禁止用嵌套语法制造隐式依赖:
- 正确:
.button:hover、.button--disabled、@media (min-width: 768px) { .button--large { padding: 12px; } } - 错误:
.button { &:hover { ... } }(Sass 嵌套)或.button .button__label(用后代选择器替代.button__label) - 性能注意:BEM 类名虽长,但现代浏览器对 class 选择器匹配极快;真正拖慢的是
*[class^="button"]这类属性选择器,BEM 天然规避了它
老项目迁移 BEM 最容易踩的三个坑
直接全局搜索替换 . 为 __ 会炸掉整个样式表。真实迁移要分层切:
- 先冻结所有全局样式(
reset.css、typography.css),它们本就不该有 BEM 类名 - 用
grep -r "\.[a-z]\+ \.[a-z]" src/找出所有后代选择器,逐个判断:是真需要父子关系(如.modal .close→ 改成.modal__close),还是纯布局干扰(如.sidebar .btn→ 提取为独立.btnBlock) - Vue/React 组件中,别把
scoped或css modules当 BEM 替代品——它们解决封装,BEM 解决可读性;二者可以共存:class="card__content"+:local(.card__content)
BEM 的复杂点不在语法,而在每次加新类名时,要多问一句:“这个类脱离当前文件,别人能看懂它属于谁、起什么作用吗?”
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











