@at-root #{&}__element是唯一安全生成BEM扁平类名的方式,因&代表完整选择器字符串,直接&__element会产出带空格的后代选择器(如.card .card__title),违背BEM单类名原则;@at-root强制提级并用#{&}插值拼接,确保输出.card__title等合法原子类名。

为什么 &__element 会编译成后代选择器而不是单类名
SCSS 的 & 永远代表“当前完整选择器字符串”,不是“块名”。在 .card { &__title { } } 中,& 确实是 .card,但 SCSS 嵌套机制会在拼接时自动加空格,结果是 .card .card__title——这是合法 CSS,但它是后代选择器,依赖 DOM 层级。BEM 要求的是原子类名 .card__title,必须显式写在 HTML 元素上才生效。
常见崩坏场景:
-
.button { &:hover { &__icon { } } }→ 编译为.button:hover__icon(非法类名,浏览器直接忽略) -
.sidebar .widget { &__header { } }→.sidebar .widget__header(语义错乱,BEM 只认.widget__header) -
.card { .header { &__title { } } }→.card .header__title(&变成.card .header,拼出错误归属)
@at-root #{&}__element 是唯一安全提级方式
@at-root 强制跳出嵌套上下文,把规则输出到 CSS 根层级;#{&} 插值则只取 & 的纯字符串值(不含空格),再拼接 __ 或 --。这才是生成合法 BEM 类名的确定性路径。
正确写法示例:
.card {
@at-root #{&}__header { color: #333; }
@at-root #{&}--loading { opacity: 0.7; }
}
→ 输出 .card__header 和 .card--loading,无空格、无嵌套、符合 BEM。
其他可用形式:
- 变量也适用:
$block: "button"; @at-root #{$block}__icon { }→.button__icon - 修饰符同理:
@at-root #{&}--disabled - 禁止在
@media、&:focus或子选择器内部再用@at-root #{&}__xxx,否则输出顺序不可控,且破坏 BEM 语义层级
为什么不能在伪类或媒体查询里用 @at-root 提元素
@at-root 本身会脱离当前作用域,如果在 &:hover 内部调用,#{&} 仍会取到 .button:hover 这个字符串,拼出 .button:hover__icon —— 依然是非法类名。更糟的是,该规则会被提到根层级,脱离了 :hover 上下文,完全失去交互意图。
真正该做的:
- 状态样式单独写:
.button__icon:hover { } - 或用
@extend复用逻辑,而不是靠嵌套派生 - BEM 的
__和--必须与块同级,它们不是结构派生,而是命名约定
容易被忽略的 HTML 同步问题
即使 SCSS 编译完全正确,只要 HTML 漏掉基础块类,比如只写 根本约束在于:BEM 的解耦效果不来自语法糖,而来自对类名显式归属的强制要求。SCSS 只负责生成正确类名,HTML 必须同步写全 class="card",样式就彻底失效。这种问题调试极难定位:DOM 上少一个 class,比逻辑错误更隐蔽。card card--featured card__title card__title--large,链路才算完整。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











