SCSS中@extend未合并选择器的根本原因是未满足匹配条件:%btn-base未声明、藏在嵌套块内、跨文件未导入或缺少%前缀;它只扩展选择器列表,不复制样式声明。

SCSS编译后出现“重复选择器但未合并”的现象,根本原因不是编译器漏合并,而是你写的代码从未触发过合并逻辑——@extend没被正确调用,或目标占位符根本不可见。
为什么@extend看起来像失效了
常见错误是写了.btn-primary { @extend %btn-base; },但编译结果里.btn-primary和%btn-base各自输出、互不关联。这不是 bug,是 @extend 的匹配机制在起作用:
-
%btn-base必须已声明,且不能藏在@media、&:hover或嵌套块内部;否则 Dart Sass 直接跳过,不报错也不合并 - 两个文件都用了
@extend %btn-base,但没通过@use显式导入定义它的模块——Dart Sass v7.0+ 默认模块隔离,跨文件引用会静默失败 - 写了
.btn-base { color: red; }(无%前缀),再@extend .btn-base:Sass 会尝试匹配,但若该类未被@import或@use引入,就当它不存在
@extend 只合并“被它驱动”的选择器,不合并样式声明
很多人误以为 @extend 是“把样式复制过去”,其实它只做一件事:把调用者的选择器,加进被继承者的选择器列表里。所以:
- 如果
%flex-center定义在.layout { %flex-center { ... } }里,那么.card { @extend %flex-center; }会生成.layout .card,而不是.card - 多个组件分别
@extend %flex-center,但其中某个在@media (min-width: 768px)内调用,Sass 不会把媒体查询提级合并,而是生成两份独立规则:@media {...} { .card { ... } }和.modal { ... } - 想让
.btn和.link共享text-decoration: none?别用@extend %no-underline,直接写@mixin no-underline { text-decoration: none; }+@include——它不碰选择器,只复用声明
真正能合并重复 CSS 的路径只有两条
一条是编写期控制,一条是构建后处理,没有“自动合并”这种捷径:
- 禁用组件内直接写
@media或@extend,改用集中管理的@mixin breakpoint($name) { @media (...) { @content; } },再配合postcss-combine-duplicated-selectors类工具做后处理 - 用
%占位符时,必须满足三个硬条件:以%开头、不在嵌套块内声明、被至少一个@extend调用;少一个,它就彻底消失,不会留下空规则 - 如果你发现编译后有几十个
.card__title都带font-size: 16px,别挣扎着改@extend,直接抽成 CSS 自定义属性:.card { --title-font-size: 16px; },子元素用font-size: var(--title-font-size);
最常被忽略的一点:所谓“重复选择器”,90% 源于你本该用原子类或自定义属性的地方,强行塞进了 @extend 的语义模型里——它解决的是“选择器复用”,不是“样式复用”。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











