BEM本质是用类名显式声明归属关系替代DOM层级依赖:.card__title不靠父容器存在生效,而.card .title因结构变动或继承污染极易失效。

直接说结论:BEM 不是让初级前端“多写几个下划线”,而是帮他们用类名代替选择器层级,把“我该选哪个父容器”这种模糊判断,变成“这个元素属于哪个块”的确定性动作。
为什么 .card .title 一复制就失效
浏览器匹配 CSS 是从右往左的:.card .title 实际在找「所有带 title 类、且父级有 card」的元素。一旦新页面结构是 <section class="product-card"><h3 class="title"></h3></section>,.title 就脱离了 .card 包裹,样式立刻断掉。
更隐蔽的问题是继承污染:.card 里设了 font-size: 0.875rem,它会通过 DOM 树影响所有子元素的 .title,但你根本没在规则里声明它,改起来像捉迷藏。
.card__title 不依赖父容器是否存在,只要 HTML 里写了这个类,样式就生效。它不靠层级“推导”作用域,而是用命名显式声明:“我只属于 card”。
立即学习“前端免费学习笔记(深入)”;
新手最容易写错的三类 BEM 类名
常见错误现象:.header-menu-item、.user-list-item、.is-active
-
.header-menu-item—— 这是连字符拼接,不是 BEM;它没体现 block 边界,menu和item谁是块?谁是元素?无法判断 -
.user-list-item—— 同样是连字符,实际应写作.user-list__item;user-list是块,item是它的元素 -
.is-active—— 纯状态类无上下文,不知道它属于谁;应写作.button--disabled或.user-card--selected,修饰符必须挂载在具体 block 或 element 上
怎么用 clsx 避免动态类名漏前缀
手拼字符串是 BEM 最大破口:className={`btn ${isDisabled ? 'btn--disabled' : ''}` 看似正确,但一旦变量名改错、条件漏写或大小写不一致(Btn--disabled),CSS 就静默失效。
必须用 clsx 或 classnames,且所有参数都基于统一 block 名生成:
const BLOCK_NAME = 'search-form';
className={clsx(BLOCK_NAME, isDisabled && `${BLOCK_NAME}--disabled`, hasError && `${BLOCK_NAME}__input--error`)}
禁止在组件内硬编码类名字符串;所有 __ 和 -- 必须由 block 名派生,不能凭记忆手写。
现有项目补 BEM 别一上来就重命名全部 class
容易踩的坑:
- 只改子元素 class,漏掉块根:写了
.card__title却保留旧的.card,导致样式残留或冲突 - 修饰符挂错位置:给
.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>
BEM 的约束力不在字符上,而在“块必须对应独立目录”“元素不可嵌套”“modifier 不可链式叠加”这些边界感里。初学者最容易忽略的,是把 BEM 当成格式校验,却忘了它本质是一套协作契约。


















