@extend不是万能钥匙,用错反增体积;安全路径是%占位符+严格边界+@mixin补位。因其编译时强制合并选择器,易引发爆炸式增长、跨文件路径污染及权重失控,且仅适用于无参、无嵌套、语义一致的原子样式复用。

直接说结论:@extend 不是解决冗余的万能钥匙,用错反而让 CSS 更臃肿;真正可控、可维护的优化路径,是「% 占位符 + 严格限制使用边界 + @mixin 补位」三者配合。
为什么 @extend 编译后 CSS 反而更大?
很多人以为 @extend 是“样式复用”,结果编译出一长串逗号分隔的选择器,比如 .btn, .btn-primary, .btn-secondary, .card-header, .modal-footer {...}。这不是 bug,是 Sass 的设计逻辑:它把所有继承同一占位符的选择器强行合并进一个规则块。
- 当多个组件(如
.btn、.link、.tag)都@extend %text-truncate,Sass 就会生成超长选择器列表,尤其在老项目里容易触达 IE 的 4095 字符限制 -
@extend会跨文件拉取祖先路径——比如%flex-center里写了& > div,被.card {@extend %flex-center}调用后,会产出.card > div,但若%flex-center定义在另一个模块,这个> div规则可能出现在完全无关的 CSS 区域 - 新版 Dart Sass(v7.0+)默认禁用跨文件
@extend,旧项目升级后直接报Undefined placeholder selector,本质是模块隔离变严,不是配置漏了
占位符 % 必须满足三个硬条件才安全
% 不是类名,也不是变量,它是纯抽象的“钩子”,只在 @extend 时被注入,自身不输出任何 CSS。但它必须守规矩,否则调试崩溃、编译失败、线上漏样式。
- 必须以
%开头声明,例如%sr-only、%clearfix;写成.sr-only或sr-only都无效 - 必须被至少一个
@extend调用,否则编译后彻底消失——你查不到它,也 debug 不到它 - 不能出现在嵌套规则内部,比如
.card { .title { @extend %heading; } }是危险操作,Sass 会尝试拼接.card .title和%heading的全部上下文路径,产出类似.card .card .title的冗余选择器
@extend 和 @mixin 到底怎么分工?
别再纠结“哪个更好”,关键看你要复用的是什么:
立即学习“前端免费学习笔记(深入)”;
- 用
@extend:仅限语义清晰、永不单独存在、无参数、不含媒体查询的原子功能类,比如%visually-hidden、%reset-list、%text-clip - 用
@mixin:只要带颜色、尺寸、断点、状态变化(&:hover)、或需要动态计算(lighten($color, 10%))——全得靠@mixin,%不支持传参,也不支持包裹@media - 混用要警惕:比如
%button-base里写了&:hover,又被.btn {@extend %button-base}和.btn-outline {@extend %button-base}同时调用,最终生成的 CSS 是.btn:hover, .btn-outline:hover,权重升高且不可拆分
调试和上线前必须检查的三件事
很多冗余问题不是写的时候埋下的,而是编译链路里悄悄放大的:
- 检查
sourcemap是否真生效:打开 DevTools → Sources → 找.scss文件,右键 “Blackbox Content Script” 再刷新,看跳转是否准确;不准就说明@use扁平化或 PostCSS 插件破坏了行号映射 - 上线前关掉
--embed-sources和--sourcemap:它们会让输出体积暴涨,只在开发期开 - 用
sass --style=expanded先输出未压缩 CSS,肉眼扫一遍是否有意外的嵌套展开(比如 5 层深的.a .b .c .d .e)或重复选择器块——compressed模式只会掩盖问题,不会消灭它
最常被忽略的一点:占位符的语义边界比代码位置更重要。%icon 看似通用,但如果它同时被按钮、表单、弹窗引用,且各自对 font-size、vertical-align 有不同要求,那它就不该是占位符,而该拆成 @mixin icon($size: 1em) —— 抽象不是越少越好,是刚好够用、改一处不影响八方。


















