@at-root #{&}__element是唯一安全生成bem扁平类名的方式,因&__element在嵌套中会产出带空格后代选择器或非法类名,违背bem单类名与同级语义原则。

直接用 &__element 在块顶层写看似能编译出正确类名,但只要嵌套层级一变、加个伪类或抽成混入,就立刻崩——这不是写法问题,是 SCSS 嵌套机制和 BEM 语义根本冲突。
为什么 &__title 在 .card { } 里“碰巧”能用?
因为 & 此时展开为 .card,而 SCSS 的嵌套规则恰好把 &__title 当作一个新选择器拼接,输出 .card__title。但这只是表象,它不表示你理解了 & 的行为边界:
-
&永远只代表“当前完整选择器字符串”,不是“块名” - 一旦写成
.card { .header { &__title { } } },&就变成.card .header,结果是.card .header__title,完全违背 BEM -
.button { &:hover { &__icon { } } }编译为.button:hover__icon,浏览器直接忽略该规则
@at-root #{&}__element 是唯一安全提级方式
所有 __ 和 -- 类名必须和块定义同级,不能被包裹在任何嵌套上下文里。否则 CSSOM 构建时会多一层依赖,HTML 里单写 class="card__title" 就失效:
- 正确:
.card { @at-root #{&}__header { color: #222; } }→ 输出.card__header - 错误:
.card { &__header { } }在@media或&:focus内使用,输出顺序错乱且语义断裂 - 变量块名也适用:
$block: "button"; @at-root #{$block}__icon { ... }
用 @mixin 封装时,& 必须由 b() 首先绑定
混入链中,e()(元素)和 m()(修饰符)的 & 值,取决于谁最先定义了它。如果没用 b() 初始化,& 就可能指向外层任意父选择器:
-
@mixin b($block) { $B: "n-#{$block}"; .#{$B} { @content; } }—— 这一步必须最先执行,它把&绑定为.n-card -
@mixin e($name) { @at-root #{&}__#{$name} { @content; } }—— 此时&才可靠,否则@include e("icon")可能产出.sidebar__icon - 禁止在
@media或&:hover内部调用e(),@at-root会脱离媒体查询上下文,导致 CSS 输出位置不可控
循环生成元素类名时,别让 & 混淆作用域
@each 里的 & 不会动态切换为循环项,它始终指向循环外最近的父选择器。想批量生成 .card__header、.card__footer,不能靠嵌套循环:
- 错误:
.card { @each $part in ("header", "footer") { &__#{$part} { } } }——&仍是.card,但整个规则被包在.card { }里,CSS 文件结构混乱 - 更稳妥:
$block: "card"; @each $part in ("header", "footer") { .#{$block}__#{$part} { } } - 注意字符串拼接必须用
+:#{$block} + "__" + $part,写成$block"__"$part会语法报错
真正容易被忽略的点是:BEM 不是命名习惯,而是作用域契约。@at-root 不是“技巧”,它是强制把类名拉回根级的唯一手段;一旦你在 &:hover 或 .card__header 内部再试图生成 __,就等于主动撕毁这份契约——编译器不会报错,但 HTML 和 CSS 之间的映射关系已经断裂。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











