Sass中&__content会编译出.card .card__content(带空格后代选择器)而非扁平类名,因&仅字符串拼接、不识别BEM语义;安全写法为@at-root #{&}__content或手动写全名.card__content。

直接用 Sass 嵌套写 &__content,90% 的人会产出 .card .card__content(带空格的后代选择器),而不是想要的扁平类名 .card__content。这不是 Sass 有 bug,而是它根本不管 BEM —— 它只按语法规则拼字符串。
为什么 &__element 会编译出带空格的选择器?
Sass 的 & 是“父选择器字符串拼接”,不是“BEM 拼接操作符”。只要嵌套层级里有换行、缩进或不小心多打一个空格(比如 & __icon 中间有空格),它就照单全收,生成带空格的选择器。
常见错误现象:
– 写 .card { &__title { color: red; } },结果 CSS 输出 .card .card__title
– VS Code 自动补全或粘贴时混入不可见 Unicode 空格,&__title 看似正常,实则失效
- 真正安全的写法只有两种:
@at-root #{&}__title或手动写死.card__title - 所有
&--modifier必须和块同级,不能塞进&:hover或&__icon里面,否则输出.card:hover--active这种非法类名 - 连字符参与拼接时要防误解析:
#{$block}__#{$el}#{"--hover"},不能写成#{$block}__#{$el}--hover(Sass 会当减法)
怎么用 @mixin 封装 BEM 拼接逻辑?
靠手写 @at-root #{&}__xxx 太重复,推荐封装成可复用的 mixin,统一管理分隔符和命名空间。
立即学习“前端免费学习笔记(深入)”;
示例(Vite + Sass):
$namespace: 'ui' !default;
$element-separator: '__' !default;
$modifier-separator: '--' !default;
@mixin b($block) {
$B: $namespace + '-' + $block;
.#{$B} {
@content;
}
}
@mixin e($element) {
@at-root #{&}#{$element-separator}#{$element} {
@content;
}
}
@mixin m($modifier) {
@at-root #{&}#{$modifier-separator}#{$modifier} {
@content;
}
}
使用时:
@include b(button) {
padding: 8px;
@include e(icon) {
margin-right: 4px;
}
@include m(primary) {
background: #007bff;
}
}
→ 编译为:.ui-button、.ui-button__icon、.ui-button--primary
- 必须确保
@include e()和@include m()只在@include b()内部调用,否则&指向错位 - 不要在
@include e()里再嵌套@include m(),比如&__icon--spin应直接写@include e(icon) { @include m(spin) { ... } },否则修饰符会挂在元素上而非块上
循环生成多个 BEM 类名时怎么避免空格陷阱?
@each 里直接写 &__#{$part} 是高危操作:它仍受外层嵌套影响,& 指向的是块选择器本身,但编译器会在其后加空格再拼接,结果还是 .card .card__header。
可靠方案只有两个:
- 强制提级:
@at-root #{&}__#{$part} - 彻底脱离
&:$block: 'card'; .#{$block}__#{$part}
同时注意:
– @each $mod in ("hover", "disabled") 必须加引号,写成 (hover, disabled) 会报 Undefined variable "$hover"
– 分隔符建议定义为变量(如 $element-separator),避免项目内硬编码不一致
BEM 的 __ 和 -- 在 Sass 里就是普通字符,没有语法特权;所有“扁平”都得靠人盯住 @at-root、插值和空格——稍一松懈,就退回 CSS 维护地狱。


















