%占位符必须顶层独立声明且仅被:extend()调用,不能嵌套或当mixin使用;它实现选择器合并以减少css体积,适用于无参静态基底样式复用,而mixin用于含参动态逻辑生成。

占位符选择器(%)怎么写才真正复用?
Less 的 % 占位符本身不会输出 CSS,只有被 :extend() 显式调用时才会“注入”到目标选择器中。它不是变量,也不是 mixin,不能直接调用或传参。常见错误是把它当普通类写成 .btn-base { %icon-reset { ... } }——这会报错,因为 % 不能嵌套在规则块里。
正确写法必须独立声明、顶层定义:
%icon-reset {
display: inline-block;
vertical-align: middle;
font-size: 0;
}
然后在需要的地方用 :extend() 引入:
.btn-icon::before {
.icon-reset();
// ❌ 错误:这不是 mixin,不能这样调
}
.btn-icon::before {
&:extend(%icon-reset); // ✅ 正确
}
:extend() 和 mixin 的关键区别在哪?
两者都减少重复,但生成结果完全不同:
-
:extend(%icon-reset)→ 合并选择器,最终 CSS 中.btn-icon::before和%icon-reset共享同一组声明,选择器列表变长(如.btn-icon::before, .menu-item::after { ... }) -
.icon-reset()(mixin)→ 复制样式块,每个调用处都生成一份完整声明,体积随调用次数线性增长
所以:跨组件共用基础视觉行为(如重置伪元素、清浮动、隐藏文本)适合用 % + :extend();含参数、需局部微调的逻辑(如不同圆角、颜色变体)必须用 mixin。
容易踩的坑:
- 跨文件
:extend(%xxx)前,%xxx必须已通过@import加载,且顺序不能颠倒 -
:extend()会提升权重:%icon-reset 权重为 0-0-0,但.btn-icon::before:extend(%icon-reset)编译后仍是.btn-icon::before,权重不变;可一旦写成.btn:extend(.disabled),就会把.disabled的权重(0-1-0)也带进来 - 不支持链式 extend:
%a:extend(%b)再.x:extend(%a),部分 Less 版本(尤其是旧版 lessc)可能无法穿透解析
为什么 % 有时反而让 CSS 更难覆盖?
因为 :extend() 是编译期合并,它把多个选择器“焊死”在同一组声明里。比如:
%text-truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.title:extend(%text-truncate) {}
.subtitle:extend(%text-truncate) {}
编译后是:
.title,
.subtitle {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
这时你想单独给 .subtitle 加 max-width: 200px,就必须写:
.subtitle {
max-width: 200px;
}
但如果项目里已有 .subtitle { color: red; } 在另一处,就可能出现权重冲突(两个 .subtitle 规则分离),或者因压缩工具合并选择器而意外覆盖。
更稳妥的做法是:只对真正“语义等价”的元素用 %,比如所有需要清空默认样式的图标伪元素、所有需统一禁用交互的 placeholder 状态——它们本就不该有差异化声明。
和 BEM 修饰符一起用要注意什么?
BEM 要求修饰符(如 .btn--large)是独立类名,而非依赖父级状态。但有人会写:
.btn {
&--large:extend(%btn-base) {}
}
这看似省事,实际生成的是 .btn--large 单独继承,没问题;但若写成:
.btn {
&--large {
&:extend(%btn-base);
}
}
多数 Less 版本会报错或忽略 —— 因为 % 必须在顶层作用域声明,不能出现在嵌套块内部。
另一个陷阱:%btn-base 若本身含属性选择器(如 %btn-base[disabled]),那 .btn--large:extend(%btn-base) 就会生成 .btn--large[disabled],而不是你想要的 .btn--large.disabled。这种细节不检查编译输出,上线后才发现点击态失效。
真正难的不是语法,是判断哪些样式属于“可安全合并的基底”,哪些本质就是“有条件分支”。后者硬塞进 %,只会让调试时 DevTools 里点开一个类,看到十几行来自不同 extend 的声明,却分不清哪条来自哪个上下文。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











