必须拆分BEM为b()、e()、m()三个独立mixin并均用@at-root提级,否则类名错位;@mixin bem($block,$element)危险因参数易乱、破坏语义、&引用错乱、生成后代选择器。

不能用单个 mixin 封装 BEM 全部逻辑,必须拆成 b()、e()、m() 三个独立 mixin,并全部依赖 @at-root 提级,否则生成的类名必然带空格或错位。
为什么 @mixin bem($block, $element) 是危险写法?
这类万能 mixin 表面省事,实际破坏 BEM 的语义约束:
-
@include bem('card', 'header')和@include bem('card', 'large')参数顺序稍乱,就可能产出.card__large(非法)而非.card--large - 它无法强制
e()只能在b()内部调用,导致&引用上下文错乱,比如在.btn:hover里调用,&指向的是.btn:hover,不是.btn - 编译结果常出现
.btn:hover .btn__icon这类后代选择器,违背 BEM 扁平原则
b() 必须作为顶层作用域入口,且只做一件事
b() 不生成样式,只设定命名空间和 & 的初始值。所有 e()、m() 都靠它提供的 & 向下传递:
@mixin b($block) {
$B: #{$namespace}-#{$block};
.#{$B} {
@content;
}
}
@include b(card) {
// 此处 & 就是 .n-card
@include e(header) { color: #333; }
@include m(large) { font-size: 1.5em; }
}
-
$namespace应统一定义为变量(如$namespace: 'n'),避免硬编码 - 禁止在
b()外直接写e()或m(),否则&无绑定目标,Sass 会报错或产出空类名 - 不推荐把
b()套在媒体查询里——@media (min-width: 768px) { @include b(card) { ... } }会导致&绑定到媒体规则,e()编译失败
e() 和 m() 必须用 @at-root + 插值,且不可嵌套
这是防止空格陷阱的唯一可靠方式。任何试图在 e() 内再调 m() 的写法(如 &__item { &--active { ... } })都会产出 .n-card__item--active,违反 BEM 规范(元素不得带修饰符):
立即学习“前端免费学习笔记(深入)”;
@mixin e($name) {
@at-root #{&}__#{$name} {
@content;
}
}
@mixin m($name) {
@at-root #{&}--#{$name} {
@content;
}
}
-
#{&}__#{$name}中的&必须紧贴__,中间不能有空格,否则变成#{&} __#{$name}→ 后代选择器 - VS Code 自动补全有时插入 Unicode 空格(如
),需手动删净;可用正则&\s+__检查源码 -
e()和m()之间不能互相调用,也不应出现在同一嵌套层级内——它们都必须是@at-root提级后的平行选择器
真正难的不是写对这三个 mixin,而是让团队所有人记住:只要没看到 @at-root 和 #{&},那行代码大概率已生成错误选择器。每次提交前用 grep -n "\. [a-z]" dist/main.css 扫一遍空格点,比调试半天更省时间。


















