bem通过将隐式依赖显式写入类名实现样式隔离与可维护性:类名如.user-card__title唯一标识模块元素,避免结构变动导致样式失效或意外影响其他模块,全局搜索可精确定位所有使用点。

因为BEM把原本藏在HTML结构、祖先类、标签类型里的隐式依赖,全部显式写进类名字符串里——改一个 .user-card__title,不会意外影响购物车弹窗的 .title,也不怕把 <h3></h3> 换成 <div role="heading"> 就失效。
<h3>为什么改 <code>.title 会连带崩掉购物车和弹窗
传统写法如 .user-card .title 或 .header h3 把样式生效条件绑死在 DOM 层级上。一旦结构微调(比如外层容器类名从 .user-card 改成 .profile-card),样式就静默消失;更危险的是,它可能意外匹配到另一个模块的 .title,导致改 A 却崩 B。
而 .user-card__title 是自解释的唯一标识符:浏览器只检查 class 属性是否包含该完整字符串,不关心嵌套深度、父级是否存在、甚至元素是否被隐藏。重构时删文件、挪组件、换框架,只要类名还在,样式就稳。
- 禁止用
.user-card .avatar这类后代选择器,必须显式写出.user-card__avatar - 禁用标签选择器:
button、p、ul全部换成带模块前缀的类名 - 模块名必须语义化且唯一,拒绝
.section、.block这类泛义词
为什么搜 user-card__avatar--rounded 就能 100% 定位所有影响点
BEM 类名是浏览器渲染的最小作用单元,不是语义标签。它让“谁写的、在哪用的、能不能动”一查即知:
- 全局搜索
user-card__avatar--rounded直接返回:HTML 模板位置、CSS 文件(通常叫user-card.module.css)、JS 动态拼接处(AST 可扫描className="user-card__avatar--rounded") - Storybook 中重跑
UserCard.stories.tsx即可验证,不用牵连ProfileCard或SearchResultItem - 删掉
user-card/目录后,样式零残留——传统写法中.sidebar .item这类规则哪怕对应 HTML 已删除,仍留在样式表里,可能意外匹配新组件
JS 动态拼接类名时最容易漏掉的三个断裂点
React/Vue 中不是逻辑写错才出问题,而是拼写和结构断裂直接导致样式丢失或错乱:
- 漏写 Block 前缀:
className={isFeatured ? 'featured' : ''}→ 应为product-card--featured - 连字符写错:
card__title--large(非法)→ 正确是card__title--large(注意双中划线只用于 modifier) - 大小写混用:
Card__header和card__header在 Linux 构建环境视为不同类名,样式丢失且无报错
建议统一用 clsx 或封装 bem('card', { featured: isFeatured }) 工厂函数,别手写模板字符串。
真正容易被忽略的是三者映射关系
BEM 的价值不在类名长度,而在「文件名、目录结构、类名」三者之间的一致映射。一旦 components/Button/Button.module.css 里出现 .input 或 .modal__header,整个约束就从工程规范退化成了装饰习惯——工具链拦不住,协作也守不住。











