@extend 在 BEM 中必然导致冗余跨块选择器,因其违背块级隔离原则,强制合并不同块的选择器,破坏语义边界、引发样式耦合、响应式失控及维护困难。

@extend 在 BEM 中几乎必然导致冗余组合,根本原因不是写法错,而是它违背 BEM 的“块级隔离”前提——它强制把不同块的类名塞进同一个 CSS 规则里,编译后出现 .section1__title, .section2__title, .card__title 这类跨块选择器,直接破坏 BEM 的语义边界。
为什么 @extend 会生成跨块选择器
@extend 不是注入样式,而是合并选择器。当你在两个不同块中分别写 .section1 { &__title { @extend %heading-sm; } } 和 .card { &__title { @extend %heading-sm; } },Sass 会把所有调用者打包进 %heading-sm 的规则块,输出:.section1__title, .card__title { font-size: 1.125rem; line-height: 1.4; }。这看起来“省代码”,但实际让 .section1__title 和 .card__title 在 CSS 层面强耦合,哪怕它们本该完全无关。
- 浏览器解析时,
.section1__title的样式权重和匹配路径被.card__title拖累,尤其在旧版 Safari 中易触发选择器长度限制 - 一旦某处删掉
.card { &__title { @extend %heading-sm; } },.section1__title的样式不会消失,但维护者无法从源码直观判断它是否还依赖这个占位符 - 如果
%heading-sm后来加了@media (min-width: 768px),所有跨块继承点都会无差别获得响应式规则,无法按块单独控制
嵌套中用 & + @extend 更危险
BEM 常见写法 .section1 { &__title { @extend %text-bold; } } 看似安全,但 Sass 编译时会尝试保留上下文拼接。若 %text-bold 定义在另一个文件且含父级结构(比如 .layout { %text-bold { ... } }),结果可能是 .section1 .layout__text-bold, .section1__title ——这既不是合法 BEM 类名,也无法命中任何 HTML 元素。
PigX UI Pro 前端开发指南 - Vue 3 + TypeScript + Element Plus。当用户提到 PigX UI、PigX 前端、lgb-mgui 项目、Vue 3 企业级后台开发、Element Plus 后台开发时使用此技能。
- 编译后出现重复前缀,如
.section1 .section1__title或.card .card__header,DevTools 里样式来源混乱 - 占位符若定义在
@media内部,@extend会把它整个“提级”到全局,导致响应式逻辑失效 - Dart Sass 7.0+ 默认禁用跨文件
@extend,旧项目升级后直接报Undefined placeholder selector,但错误位置往往指向调用处而非定义处,排查成本高
真正符合 BEM 的复用方式
要共享 __title 这类元素样式,必须保持块级独立性。可行路径只有两条:显式并列声明,或提取为工具类。
- 用逗号合并多个块选择器:
.section1, .section2, .card { &__title { font-size: 1.125rem; } },编译后仍是合规 BEM,且修改一处即全局生效 - 放弃 Sass 层继承,改用 HTML 多类组合:
<h2 class="section1__title text-h4">,把.text-h4定义为独立工具类,不带任何 BEM 结构约束 - 绝对不用
@extend .section1__title或@extend %section1-title——只要目标占位符名字带块名,就等于主动放弃 BEM 隔离
最易被忽略的一点:BEM 的“块”是语义单元,不是样式容器。@extend 把它当成了样式容器来复用,这就从根上错了。编译后的 CSS 是否干净,不取决于 SCSS 写得多简洁,而取决于你是否让每个块的样式输出严格限定在自己的命名空间内。

















