最安全的bem类名生成方式是用@each遍历字符串数组配合@at-root #{&}__element;&__element会生成带空格的后代选择器,违反bem扁平化原则,必须避免。

直接用 @each 遍历字符串数组生成 BEM 类名,是最安全、最可控的方式;&__element 这类嵌套写法会产出带空格的后代选择器,根本不符合 BEM 规范。
为什么 &__element 不能用
SCSS 的 & 表示当前完整父选择器字符串,不是“块名”。写 .card { &__header { } },编译结果是 .card .card__header(带空格),这不是单类名,而是后代选择器。BEM 要求的是扁平、无依赖的 .card__header,一旦 HTML 中没写外层 .card,样式就完全失效。
- 常见错误:
.button { &:disabled { &__icon { } } }→ 输出.button:disabled .button__icon,但实际需要的是.button--disabled .button__icon或独立的.button__icon规则 - 伪类里嵌套
&__xxx更危险:.card:hover { &__body { } }编译为.card:hover .card__body,性能差且语义错乱 - 真正生效的 BEM 元素类必须是独立单类名,不能靠 DOM 层级推导
@at-root #{&}__element 是唯一可靠写法
必须跳出嵌套上下文,在根层级生成目标类名。这是目前唯一能稳定产出 .block__element 格式的选择器的方式。
- 正确示例:
.card { @at-root #{&}__header { color: #333; } }→ 输出.card__header - 修饰符同理:
@at-root #{&}--loading { opacity: 0.7; }→.card--loading - 若块名来自变量:
$block: "modal"; @at-root #{$block}__close-button { … } - 禁止在
@media、&:focus等子上下文中再套@at-root #{&}__xxx,输出顺序不可控,且破坏 BEM 层级语义
修饰符列表必须是带引号的字符串数组
BEM 修饰符(如 hover、disabled)是字面量,不是变量。不加引号会被 SCSS 当作未定义变量处理,直接报错 Undefined variable "$hover"。
- 错误写法:
@each $mod in (hover, disabled) { .btn--#{$mod} { } }→ hover 被当变量 - 正确写法:
@each $mod in ("hover", "disabled", "large") { .btn--#{$mod} { } } - 如果从 map 取 key:
map-keys($modifiers)返回的仍是无引号标识符,需额外转字符串,不如直接定义字符串数组稳妥 - 连字符拼接必须用插值:
.btn--#{$mod}✅,.btn--$mod❌(漏#{}会原样输出文字)
嵌套元素名别用 @for,优先用 @each 配合字符串数组
BEM 元素名是语义化字符串(header、footer),不是数字索引。用 @for 不仅多写逻辑,还容易因 nth($list, $i) 取值失败导致整个编译中断。
- 错误示范:
$parts: header, footer; @for $i from 1 through length($parts) { &__#{nth($parts, $i)} { } }→header没引号,报错 - 正确做法:
@each $part in ("header", "footer", "body") { @at-root #{&}__#{$part} { } } - 注意:连字符
-不是字符串连接符,"btn"__"icon"语法错误;必须用#{&}__#{$part}这种双插值形式 - 所有
__和--类必须与块定义同级,不能嵌套进任何状态或容器规则中
最容易被忽略的是:BEM 的解耦效果不来自 SCSS 语法糖,而来自对类名归属的强制显式声明。哪怕编译出来的 CSS 完全正确,只要 HTML 没同步写全 card card--featured card__title card__title--large,整个结构就断链——这种问题不会报错,只会在运行时静默失效,调试成本极高。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











