SCSS的&符号默认拼接完整父选择器并保留空格,导致.card { &__title {} }编译为.card .card__title(后代选择器),违背BEM扁平化要求;正确做法是用@at-root#{&}__title生成无空格的.card__title。

SCSS 的 & 符号不能直接拼出 BEM 的 __ 和 -- 结构,必须配合 @at-root 和插值才能安全生成合法类名。
为什么 &__content 会编译出错误选择器
SCSS 默认把 & 当作完整父选择器字符串,带空格拼接。比如:
.card {
&__title { color: red; }
}
编译结果是 .card .card__title(后代选择器),而 BEM 要求的是 .card__title(单个类名)。这不是 bug,是嵌套机制的固有行为。
- 空格意味着层级关系,破坏 BEM 的扁平命名原则
- 伪类中误用(如
&:hover &__item)会产出.nav:hover__item这类非法名 - 一旦父选择器含路径(如
.sidebar .widget),&就会把整个路径都拼进去
正确写法:用 @at-root + 插值提级
必须跳出嵌套上下文,在根层级生成目标类名。安全模式如下:
.card {
@at-root #{&}__title { color: red; }
@at-root #{&}--hovered { background: #f0f0f0; }
}
输出为 .card__title 和 .card--hovered,无空格、无嵌套污染。
- 变量块名也适用:
$block: "button"; @at-root #{$block}__icon { ... } - 避免在
&:focus或&[disabled]内部再用&__xxx - 所有
--修饰符必须和块同级,不能出现在元素内部嵌套里
何时禁用 & 拼接,改用手写全名
当组件结构复杂、或需跨文件复用时,硬编码反而更可控:
- 多人协作项目中,
@at-root+ 插值易导致 CSS 输出顺序混乱,尤其在多@import场景下 - 调试时无法直接搜索
.card__title定位样式,因为源码里是#{&}__title - Vue / React 单文件组件中,若
scoped+ BEM 混用,手写全名可避免data-v-xxx干扰匹配逻辑
推荐底线:只要 HTML 中明确写了 class="card__title",SCSS 里就该写 .card__title,不依赖预处理器“猜”结构。
混入(@mixin)封装的隐患点
全局 @mixin b() / e() / m() 看似省事,但实际埋了几个坑:
-
@include e("title")在嵌套块内调用时,&可能已不是原始块名(比如在.card-header里调用,&就是.card-header) - 修饰符混入
@include m("large")若未加@at-root,会生成.card .card--large - 所有混入必须强制要求调用位置处于块定义顶层,否则语义失效
真正容易被忽略的是:BEM 的有效性不取决于 SCSS 写得多巧,而在于 HTML 类名是否严格匹配 CSS 选择器——哪怕只差一个空格,样式就断连。写完务必检查编译后 CSS 是否真有 .block__element 这种零空格形式。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











