@at-root #{&}__element是唯一安全生成bem扁平类名的方式,因&__item会编译为带空格的后代选择器.card .card__title,违背bem单类名原则;伪类或路径父级中误用更会导致非法类名或语义错乱。

直接用 & 嵌套生成 __ 或 -- 类名是错的,编译结果会带空格(如 .card .card__title),违背 BEM 扁平化原则;唯一可靠方式是 @at-root 配合插值。
为什么 &__item 会编译出后代选择器
SCSS 的 & 表示“当前完整选择器字符串”,不是“块名”。在 .card { &__title { } } 中,& 确实是 .card,但嵌套机制天然加空格,结果是 .card .card__title —— 这是合法 CSS,但不是 BEM 要的单类名。
- 空格意味着 DOM 层级依赖:若 HTML 里只写
<div class="card__title">,样式完全不生效 <li>伪类中误用更危险:<code>.button { &:hover { &__icon { } } }编译为.button:hover__icon,浏览器直接忽略 - 父选择器含路径时彻底失控:
.sidebar .widget { &__header { } }→.sidebar .widget__header,语义错乱 - 正确写法:
.card { @at-root #{&}__title { color: #333; } }→ 输出.card__title - 修饰符同理:
@at-root #{&}--loading { opacity: 0.7; }→.card--loading - 变量块名也适用:
$block: "button"; @at-root #{$block}__icon { ... } - 禁止在
&:focus、@media内部再用@at-root #{&}__xxx,输出顺序不可控且破坏语义 - 先统一配置:
$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") { ... } },避免&上下文失控 - 正确:
@each $modifier in ("hover", "disabled") { .btn--#{$modifier} { ... } } - 错误:
@each $modifier in (hover, disabled) { ... }(hover 被当变量) - 从 map 取 key 时需确认类型:
map-keys($mods)返回的是无引号标识符,应改用map-to-list($mods)或手动包裹引号 - 拼接连字符必须用
+:.#{$block}__#{$elem}✅,.#{$block}__icon仅当$block已定义才安全
怎样用 @at-root 安全生成 __ 和 --
必须跳出嵌套上下文,在根层级拼接。所有 __ 和 -- 类名都得和块定义同级,不能嵌套进状态、容器或媒体查询里。
如何封装可复用的 @mixin 支持命名空间与分隔符
硬编码 __ 或写死 el- 前缀会导致跨项目难以迁移。真正可维护的封装必须把命名空间、分隔符、状态前缀全部参数化,并显式传入块名。
循环生成 BEM 类名时插值要加引号吗
必须加。修饰符名(如 "hover"、"disabled")是字符串字面量,不加引号会被 SCSS 当作变量解析,报 Undefined variable "$hover"。
最容易被忽略的是:BEM 的解耦效果不来自 SCSS 语法糖,而来自对类名归属的显式约束——哪怕编译输出全对,只要 HTML 漏写 card 基础类,或忘记同步加 card--featured,样式链就断了,且极难调试。











