@at-root #{&}__element是唯一安全生成bem类名的方式,因&始终代表完整选择器字符串,直接&__title会编译为.card .card__title(带空格后代选择器),违背bem扁平化单类名原则。

直接用 @at-root + #{&} 拼接是唯一能稳定生成合法 BEM 类名的方式,其他任何嵌套写法(包括 &__item、&--mod 放在伪类里)都会产出带空格的后代选择器或非法类名,破坏 BEM 的扁平化前提。
为什么 &__title 在顶层也编译出 .card .card__title
SCSS 的 & 表示“当前完整选择器字符串”,不是“块名”。哪怕写在 .card { &__title { } } 这种最外层位置,SCSS 仍按嵌套语义处理:它把 & 当作父选择器,加空格后拼接子串,结果必然是 .card .card__title。这不是 bug,是设计行为——但和 BEM 要求的单类名 .card__title 冲突。
常见翻车点:
-
.card { .header { &__title { } } }→ 编译为.card .header__title,语义错乱 -
.button { &:hover { &__icon { } } }→ 编译为.button:hover__icon,浏览器直接忽略 -
.sidebar .widget { &__header { } }→ 编译为.sidebar .widget__header,但 BEM 只认.widget__header
怎样用 @at-root 安全生成 __ 和 --
必须跳出嵌套上下文,在 CSS 根层级拼接。所有 __ 和 -- 类名都得和块定义同级,不能嵌套进状态、容器或媒体查询里。
正确写法:
-
.card { @at-root #{&}__title { color: #333; } }→ 输出.card__title -
.button { @at-root #{&}--loading { opacity: 0.7; } }→ 输出.button--loading - 变量块名也适用:
$block: "button"; @at-root #{$block}__icon { }→.button__icon - 禁止在
&:focus或@media内部再用@at-root #{&}__xxx,输出顺序不可控且破坏语义
如何封装可复用的 @mixin 支持命名空间与分隔符
硬编码 __ 或写死前缀会导致跨项目难以迁移。真正可维护的封装必须把命名空间、分隔符、状态前缀全部参数化,并显式传入块名。
基础配置:
$namespace: "n";$element-separator: "__";$modifier-separator: "--";$state-prefix: "is-";
主混入必须接收 $block:
@mixin bem($block) {
$B: #{$namespace + "-" + $block};
.#{$B} {
@content;
}
}
元素混入内必须用 @at-root 提级:
@mixin element($name) {
$selector: &;
@at-root #{$selector + $element-separator + $name} {
@content;
}
}
调用时强制显式传参:
@include bem("card") { @include element("header") { } }- 避免
&上下文失控,不依赖隐式嵌套
BEM 的解耦效果不来自语法糖,而来自对类名显式归属的强制约束。哪怕 SCSS 编译出正确的 CSS,如果 HTML 没同步写全 card card--featured card__title card__title--large,整个链路就断了——DOM 上少一个 class,比逻辑错误更隐蔽,也更难调试。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











