SCSS中直接使用&__content会因空格生成后代选择器而非BEM要求的单类名,正确做法是用@at-root #{&}__title强制拼接;Emmet BEM模式可自动生成语义化HTML结构。

SCSS里直接写&__content会生成错误选择器
它编译出来是.card .card__content(带空格),而不是BEM要求的.card__content(单类名)。这不是bug,是SCSS嵌套机制的默认行为:&代表当前完整父选择器字符串,后面接__content只是简单拼接,空格被原样保留,结果变成后代选择器。
常见翻车场景包括:
-
.card { .header { &__title {} } }→ 编译为.card .header__title,完全偏离BEM语义 -
.card { &:hover { &__body {} } }→ 产出.card:hover .card__body,既违反扁平原则,又增加渲染开销 - 父选择器含空格或属性时,如
.sidebar .widget { &__item {} }→ 得到.sidebar .widget__item,但BEM只认.widget__item
用@at-root + 插值才是安全拼接方式
必须跳出嵌套上下文,在CSS根层级生成目标类名。核心写法是@at-root #{&}__title,其中#{&}强制取当前块名(不含空格),再拼接__title。
实操要点:
立即学习“前端免费学习笔记(深入)”;
- 块名是变量时,必须用
$block: "button"+@at-root #{$block}__icon,不能直接&__icon - 修饰符同理:
@at-root #{&}--disabled→.button--disabled - 禁止在伪类内部再用
&__xxx,比如&:hover { &__icon {} }会生成非法类名.button:hover__icon - 所有
__和--必须与块同级,不能嵌套进子容器、媒体查询或&:focus块内
循环生成BEM类名时&容易指错对象
@each或@for里用&__#{$part},&仍指向循环外最近的父选择器,不是循环变量本身。例如:
.card {
@each $part in ("header", "footer") {
&__#{$part} { /* 这里&还是.card,但规则被包裹在.card块里,实际输出.card .card__header */ }
}
}更可靠的做法是显式定义块名变量:
- 先设
$block: "card" - 再用
.#{$block}__#{$part}直接构造选择器 - 避免
@for遍历元素名——它生成数字索引,而BEM元素名是语义字符串,优先用@each
Emmet BEM模式比SCSS嵌套更省事
编辑器启用emmet.bem后,输入.btn__text或.btn--primary,Emmet会自动生成对应HTML结构(如<div class="btn__text">或<div class="btn btn--primary">),天然匹配BEM语义。
关键配置点:
- VS Code:设置中搜
emmet.bem并开启;WebStorm在Settings → Editor → Emmet → HTML勾选Enable BEM actions - 仅对
.开头的class缩写生效,#或标签名前缀不触发 - 父块名含连字符(如
.user-profile)也能正确解析子元素:.user-profile__avatar - 禁用该模式后,
.btn__text就变成普通类名,不再自动补全基础块类——务必确认配置已生效
BEM类名的稳定性不依赖预处理器“猜”结构,而是靠HTML中明确写的class="block__element"。一旦项目涉及多人协作、scoped样式或跨文件@import,手写全名反而更可控、更易搜索、更少意外覆盖。


















