bem是将归属、角色、状态硬编码进类名的协作规范:如header__logo--dark明确表示header块内的logo元素启用dark变体,通过__和--提供工具链识别边界,杜绝命名冲突与样式耦合。

直接说结论:BEM 不是让 CSS 更“高级”的装饰,而是把“归属、角色、状态”三个协作关键信息硬编码进类名里——header__logo--dark 一眼就能看出这是 header 块里的 logo 元素,当前启用 dark 变体;不需要点开 HTML、不用翻 JS、更不用问同事。
为什么 .btn 和 .button 在协作中必然引发冲突
泛义类名没有上下文。多人并行时,.btn 可能被登录页、弹窗、表单组件各自复用,但没人能保证它在所有地方都该有 margin-right: 8px。一旦某人加了 .btn--primary,另一个人却在 JS 里写 el.classList.add('btn'),结果就是按钮在某个模块突然变宽——审查元素只看到两个 .btn,根本分不清哪个生效。
使用 BEM 后,每个组件自带命名空间:
-
.auth-form__submit只属于登录表单,不会影响.settings-form__submit -
.card__title和.modal__title是完全隔离的,哪怕样式规则一模一样
__ 和 -- 不是风格偏好,是工具链识别边界
PostCSS 插件、VS Code BEM 自动补全、stylelint-bem-naming,都依赖双下划线和双破折号做语法解析。写成 .menu_item 或 .button-disabled,工具就无法识别这是元素还是修饰符,也就没法自动校验、生成类型定义或做重构提示。
常见错误现象:
- 用单下划线:
.user_profile→ 工具当普通单词,不认为是user块下的profile元素 - 修饰符混用单位:
.input--width-200px→ 换成 rem 或响应式后必须全量替换,且语义丢失(到底是“宽”还是“紧凑态”?) - 块名带样式描述:
.red-card→ 后续换主题时得重命名整个块,而不是只改--theme-dark
React/Vue 里拼接 className,为什么不能手写模板字符串
硬编码 class="card__body" 或 JS 里写 el.classList.add('card__body') 是最大隐患:重构块名时漏改一处,样式就断掉,且无报错。
正确做法:
- React 推荐用
clsx:className={clsx('button', { 'button--loading': loading })} - Vue 3 的
:class可直接绑定对象::class="{ 'modal__overlay--visible': visible }" - 禁止在 JS 里硬编码 BEM 字符串,应抽成常量或工具函数,否则重构时漏改一处就埋雷
BEM 最容易被忽略的致命点:它不管加载顺序
.card__title--large 和 .card__title--small 冲突,不是命名错了,而是 CSS 文件引入顺序或层叠权重没管住——BEM 只管名字,不管谁覆盖谁。
真正解法是放弃“哪个类名后写就生效”的直觉,改用构建时确定顺序:
- Webpack 里用
mini-css-extract-plugin控制 CSS 提取顺序 - 用
@at-root避免 SCSS 嵌套污染:.card { @at-root .card__header { } },而不是靠缩进模拟结构 - grep -r "\.[a-z]\+ \.[a-z]" dist/ 快速揪出残留的空格选择器(如
.card .card__title),这类写法浏览器匹配极慢
工具链可以焊死格式,但 BEM 的协作价值,最终取决于团队是否守住“修饰符永远叠加在块或元素上”“每个 __element 必须有对应 CSS 规则”“禁止跨块复用类名”这三条底线——松动一点,就退回命名混乱的起点。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











