单个@mixin bem()封装BEM危险,应拆为b()、e()、m()三个严格绑定&上下文的独立mixin:b()声明块作用域,e()生成__元素,m()生成--修饰符,均需@at-root提级并统一管理分隔符。

直接用单个 @mixin bem($block, $element, $modifier) 封装 BEM 是危险的——它会把语义搞错、嵌套失效、调试时根本找不到对应类名。正确做法是拆成三个严格绑定 & 上下文的独立 mixin:b()、e()、m(),每个只干一件事。
为什么不能写一个万能 bem() mixin
参数顺序一错,生成的类名就完全不对。比如 @include bem('card', 'header') 和 @include bem('card', null, 'hover') 看似只是传参差异,实际编译结果分别是 .card__header 和 .card--hover,但如果你的 mixin 内部用 @if 判断逻辑不严密,很容易产出 .card__hover 这种非法名。
更关键的是,BEM 要求 .card__header 必须在 .card { } 作用域内定义,否则无法保证样式隔离;而单 mixin 返回的是扁平选择器,& 在里面根本不起作用。
- 修饰符挂载位置错误:比如想表达
.card--large .card__body,单 mixin 很可能拼出.card--large__body - 无法约束调用层级:
e()必须出现在b()内部,单 mixin 没法强制这个关系 - 分隔符(
__、--)硬编码在 mixin 里,改起来要全局搜
b()、e()、m() 各自该干什么
b() 负责声明块级作用域并设置命名空间;e() 只能在 b() 内部调用,靠 & + @at-root 生成 .ns-block__element;m() 同理,只生成 .ns-block--modifier,禁止嵌套进 e() 里。
立即学习“前端免费学习笔记(深入)”;
示例写法:
PigX UI Pro 前端开发指南 - Vue 3 + TypeScript + Element Plus。当用户提到 PigX UI、PigX 前端、lgb-mgui 项目、Vue 3 企业级后台开发、Element Plus 后台开发时使用此技能。
@mixin b($block) {
$B: #{$namespace}-#{$block};
.#{$B} {
@content;
}
}
@mixin e($name) {
@at-root #{&}__#{$name} {
@content;
}
}
@mixin m($name) {
@at-root #{&}--#{$name} {
@content;
}
}
-
b()必须先执行,它设定了&的值,后续e()和m()才能正确引用 -
e()和m()都必须用@at-root提级,否则会编译成带空格的后代选择器(如.card .card__header) - 所有分隔符(
$element-separator: '__')应统一定义为变量,避免散落在各处
修饰符 + 元素组合怎么写才合法
BEM 允许 .card--large .card__header,但禁止 .card--large__header。遇到这种组合,不能在 m() 里再 @include e() —— 那会直接产出非法名。
正确方式只有两种:
- 用
@at-root显式拼接:@at-root #{&}--large #{&}__header { @content; } - 或更稳妥地用变量:
.#{$B}--large .#{$B}__header { @content; } - 绝对不要写
&--large { @include e(header) { } },这等于告诉 SCSS 把--large当作父级一部分去拼__header
伪类(如 :hover)、属性选择器(如 [data-id])内部禁用 &__xxx,因为 & 会解析成带括号的字符串,生成类似 .btn:hover__text 的非法 CSS。
容易被忽略的命名空间和插值细节
$namespace 不是可选项,而是复用前提。没它,b('card') 编译出来就是 .card,一旦项目里多个模块都叫 card,样式就撞车了。
插值必须用 #{&},不能写成 &__title——后者会被 SCSS 当作文本拼接,结果是 .card .card__title(带空格),破坏 BEM 扁平原则。
还有一个隐性坑:如果块选择器本身含空格或复杂结构(比如 .sidebar .widget),& 就会把整个路径都带进去,@at-root #{&}__content 会输出 .sidebar .widget__content,这不是你想要的。所以 b() 必须始终作用于单一 class 名,不接受复合选择器。

















