Less 的 detach ruleset 是一种惰性样式容器,用 @name: { ... }; 定义,不自动输出 CSS,必须显式调用(如 @name();)才展开;它既不是存储值的变量,也不是默认执行的 mixin,而是支持嵌套、&、媒体查询内调用的可复用样式块。

什么是 Less 的 detach ruleset?它不是变量也不是 mixin
Less 的 detach ruleset 是一种特殊语法,用 @ruleset-name: { ... }; 定义,它不会立即输出 CSS,而是把样式块“存起来”,等你显式调用(比如用 .selector { @ruleset-name(); })才展开。它和 @variable 不同:后者只能存值;也和 .mixin() 不同:后者默认会参与 CSS 输出(除非加 private 或命名空间隔离),而 detach ruleset 天然惰性、无副作用。
关键点在于:它支持参数、嵌套、访问父选择器(&),且可被多次复用在不同上下文中——这才是实现真正可组合 CSS 块的核心能力。
怎么定义带参数的 detach ruleset 并安全复用
直接写带参数的 ruleset 会报错:Detached rulesets cannot have parameters。正确做法是把它包裹进一个 mixin,再在 mixin 内部引用 ruleset:
@shadow-sm: {
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
};
.shadow(@color: #000) {
@shadow-custom: {
box-shadow: 0 2px 4px fade(@color, 20%);
};
@shadow-custom();
}
.card {
@shadow-sm();
.shadow(#333);
}- 不能直接写
@shadow(@c) { ... }—— Less 不允许 - 必须用 mixin 封装 ruleset,才能传参;ruleset 本身只负责“内容组织”
- ruleset 内部可自由使用
@color等外部作用域变量,但要注意作用域链:mixin 调用时的上下文决定变量取值 - 如果 ruleset 需要访问父选择器(比如想给
.btn:hover加样式),必须在调用处用&:hover { @ruleset() },ruleset 内不能写&
为什么 detach ruleset 比普通 mixin 更适合抽象“视觉模式”
比如一组按钮状态样式(default / hover / active / disabled),用普通 mixin 会强制生成所有状态,哪怕只用其中两个;而 detach ruleset 可按需拼接:
立即学习“前端免费学习笔记(深入)”;
@btn-base: {
padding: 8px 16px;
border: none;
font-weight: 500;
};
@btn-hover: {
&:hover {
opacity: 0.9;
transform: translateY(-1px);
}
};
@btn-active: {
&:active {
transform: translateY(0);
}
};
.primary-btn {
@btn-base();
@btn-hover();
@btn-active();
}
.secondary-btn {
@btn-base();
@btn-hover();
/* 不加 @btn-active,也不生成对应 CSS */
}- 每个 ruleset 是独立的、可选的片段,不耦合;mixin 往往隐含“全量应用”语义
- 编译后 CSS 更精简:没调用的 ruleset 完全不输出
- 适合构建设计系统中的原子样式层(如
@focus-ring、@sr-only),由上层组件按需组装 - 注意:ruleset 不能包含
@media查询本身(会报错),但可以放在@media内部调用,比如@media (max-width: 768px) { @btn-base(); }
容易踩的坑:作用域、嵌套和编译时机
Detach ruleset 最隐蔽的问题来自作用域泄漏和嵌套意外展开:
- 在
@media或@keyframes内部定义 ruleset,它仍属于全局作用域,可能被外部误调用 - ruleset 内部若写
.child { color: red; },调用时会原样展开;但若写& .child,则依赖调用处是否有父选择器 —— 没有就编译失败 - 不能在
@import的文件里定义 ruleset 后,指望主文件直接调用:Less 默认不提升跨文件 ruleset,需确保 import 顺序,或统一收口到一个mixins.less中 - 调试技巧:临时加
@debug "called";到 ruleset 内部,看是否被意外触发(@debug在 ruleset 里有效)
真正难的是让 ruleset 既保持语义清晰,又不因嵌套层级或变量来源变得不可预测。建议每个 ruleset 只做一件事,命名体现用途(如 @text-truncate),而不是技术特征(如 @ellipsis)。


















