bem 是通过命名契约实现样式隔离的机制——类名错误导致归属丢失而非样式失效;其核心在于 block 上下文绑定、element 依附性与 modifier 状态表达,脱离则退化为伪 bem。

BEM 不是“防冲突工具”,它是把冲突变成可定位结构错误的命名契约——类名一写错,就不是样式不生效,而是根本找不到归属。
为什么 .button 一定会覆盖 .user-card__avatar
浏览器不认“谁写的 CSS”,只按类名字符串完全匹配。只要两份样式都定义了 .button,后加载的规则就赢;而 .user-card__avatar 和 .profile-card__avatar 是两个完全不同的字符串,天然不匹配、不覆盖。
-
.button:hover在 A 模块改背景色 → B 模块所有按钮跟着变,因为没 block 上下文绑定 -
.user-card__avatar只响应user-card模块内定义的样式,哪怕profile-card也写了__avatar,它们互不感知 - 错误写法:
.card .title—— DOM 加个 wrapper 就失效;正确是.card__title,不依赖父级结构
怎么写才算真 BEM,而不是“看起来像”
伪 BEM 最常出现在类名带双下划线却没 block 上下文,或 modifier 脱离 block 单独使用——这类写法失去隔离能力,调试时根本看不出样式来源。
-
search-form__input--disabled✅(search-form是 block,input是其 element,disabled是状态) -
search-form__input_error❌(单下划线非标准,应为--error) -
button--primary❌(缺少 block 前缀,button不是合法 block 名,应为checkout-button--primary) -
header__nav-item_active❌(nav-item是另一个组件,不该塞进header的命名空间)
JS 动态拼 className 容易踩哪些坑
手拼字符串看着快,实际极易漏空格、错连字符、大小写混用,而且构建环境对大小写敏感度不同,本地跑得通,CI 上直接挂。
- ❌ 危险写法:
className={`button button--${variant} ${hasIcon ? 'button__icon' : ''}`}(缺空格、无防错、button__icon脱离 block) - ✅ 推荐用
clsx或封装常量:const BLOCK = 'search-form'; const cn = (e, m) => `${BLOCK}${e ? '__' + e : ''}${m ? '--' + m : ''}` - Vue/React 中避免直接写
class="search-form__input"后再 JS 控制显示隐藏——要用search-form__input--hidden这类 modifier,保持结构一致
微前端里 BEM 的 block 名必须动态绑定子应用 ID
单靠 BEM 命名无法避免微前端跨应用类名冲突。硬编码 sales-portal__search-input 会导致部署路径变更后失效。
- 正确做法:从微前端注册配置中读取,如 qiankun 的
name字段,或通过window.__MICRO_APP_NAME__注入到子应用运行时 - 构建时可用 Webpack
DefinePlugin注入:APP_ID: JSON.stringify(process.env.APP_ID),再在 SCSS/JS 中引用 - 禁止用项目目录名(如
src-sales)或 Git 分支名生成 block 前缀——它们和运行时不相关,CI 构建会出错
真正难的不是记规则,而是 block 名是否语义唯一、element 是否始终依附于 block、modifier 是否只表达状态——这些点一旦松动,BEM 就退化成“加长名字的仪式”。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











