唯一稳定生成合法BEM类名的方式是@mixin b()配合@at-root #{&}__,因&始终代表完整选择器而非块名,嵌套或拼接写法在层级变化、伪类或复用时必然崩坏。

为什么直接在@mixin里拼接BEM类名容易出错
因为 Sass 的 & 在 @mixin 定义体内部不解析为宿主选择器,写 #{&}__element 会报错或输出字面量 &__element。BEM 类名必须在调用时由上下文决定,不能靠 mixin 内部“猜”。
如何用@include + 参数生成合法 BEM 块/元素/修饰符结构
把块名作为必传参数,元素和修饰符用可选命名参数控制生成逻辑:
@mixin bem($block, $element: null, $modifier: null) {
@if $element and $modifier {
#{$block}__#{$element}--#{$modifier} { @content; }
} @else if $element {
#{$block}__#{$element} { @content; }
} @else if $modifier {
#{$block}--#{$modifier} { @content; }
} @else {
#{$block} { @content; }
}
}
-
$block必须传,比如"card";$element和$modifier都是null默认值,按需传入 - 调用时写
@include bem("button", $element: "text")→ 输出.button__text - 别传空字符串:
$element: ""会导致.button__这种非法类名,Sass 不报错但 CSS 无效 - 不支持嵌套调用:一个
@include bem只能生成一层选择器,想写.card__header .card__title得拆成两个@include
为什么不能用 @extend 强制统一 BEM 命名
@extend 会把选择器合并提升,破坏 BEM 的独立性。例如 .btn__icon 继承 .icon 后,编译结果可能变成 .btn__icon, .icon,导致样式泄漏或权重失控。
- BEM 的核心是“语义隔离”,每个块自包含,
@extend天然违背这点 - 团队协作中,
@extend产生的选择器链难以追溯,审查时看不出哪个文件定义了.icon - 真正需要复用的是样式逻辑(如间距、圆角),不是类名结构——这些该用变量或基础 mixin 封装
实际项目中怎么让设计师和前端对齐 BEM 命名
光靠 mixin 不够,得配合约定和工具链:
立即学习“前端免费学习笔记(深入)”;
- 在项目根目录放
_bem.scss,只暴露@mixin bem和一组预设块名变量(如$blocks: ("button", "card", "form")) - CI 流程加 ESLint 插件检查类名是否符合
^[a-z]+([__][a-z]+)*(--?[a-z]+)*$正则 - 禁止在 HTML 中手写
class="card__header--large",全部走组件封装(如 Vue 的<cardheader size="large"></cardheader>) - 最易被忽略的一点:BEM 是命名规范,不是架构方案。mixins 只解决“怎么写对”,不解决“该不该拆块”——后者得靠组件设计评审,不是 Sass 能兜底的


















