BEM通过block__element--modifier三段式命名强制语义隔离,解决CSS全局作用域下的命名冲突;Block需唯一且语义明确,Element须依附Block,Modifier表视觉变体而非状态;迁移宜新老共存、边界收敛;可与Tailwind混用但职责分明;嵌套超两级违背原则,应拆分为新Block。

为什么 BEM 能解决命名冲突,而不是靠“约定”
因为 CSS 是全局作用域,.btn 在组件 A 和组件 B 里都可能出现,但含义可能完全不同。靠口头约定或文档约束不了开发者手快打错一个字母,而 BEM 强制把语义拆成三段:block__element--modifier,天然隔离层级和意图。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- Block 名必须是唯一、语义明确的名词,比如
search-form,不能叫form或search - Element 必须依附于 Block,不能跨 Block 使用,
search-form__input不能被header直接复用 - Modifier 不是状态开关,而是视觉/行为变体,
button--primary合理,button--loading就容易和 JS 状态耦合过紧
怎么把老项目逐步迁移到 BEM,不推倒重来
全量重命名成本高、风险大,真正可行的是“新老共存 + 边界收敛”。关键不是改完所有文件,而是守住新增代码的入口。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 在团队 ESLint 配置中加
stylelint-selector-bem-pattern规则,只对新写的.scss或.css文件生效 - 给旧组件加 wrapper class,比如把遗留的
.nav包进<nav class="header-nav"></nav>,后续扩展用header-nav__item - 禁止在 BEM block 内部写类型选择器,比如
.card h3—— 这会破坏 block 的封装性,必须写成card__title
BEM 和 CSS-in-JS / Tailwind 混用时的坑
很多人以为用了 Tailwind 就不用 BEM,其实错在混淆了“样式组织”和“结构语义”。Tailwind 解决原子类组合,BEM 解决组件边界定义,二者职责不同。
常见错误现象:
- 在 React 组件里用
className="bg-blue-500 text-white p-2"写满逻辑,结果同一个按钮在三个地方长得一样、行为却不同,没人敢动 - 用
tw`flex items-center ${isActive ? 'text-red-600' : ''}`把样式逻辑塞进模板,导致 BEM 的 modifier 失效
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 用 BEM 定义组件名(
user-card),用 Tailwind 实现具体样式,但 modifier 仍走 class 切换:user-card user-card--featured - 避免在 JSX 中拼接条件 class,改用对象写法:
className={clsx('user-card', { 'user-card--featured': isFeatured })}
为什么 block__element--modifier 里不能嵌套太深
超过两级 element(比如 modal__content__title)就违背了 BEM 原则 —— 它意味着这个 title 已经脱离了 modal 的语义控制,该独立成新 block。
性能与可维护性影响:
- CSS 选择器越长,浏览器匹配越慢,尤其在大量 DOM 下,
.sidebar__list__item__link比.sidebar-list-item__link多一次层级查找 - 重构时难定位:当
__item被抽成独立组件,所有带__item__link的样式都要重写 - Vue / React 单文件组件里,scoped style 本可隔离,但过度嵌套反而让
:deep()变得不可控
记住一点:BEM 的 “__” 不是 HTML 结构的映射,而是语义从属关系。HTML 可以嵌套五层,class 名里最多一层 __。


















