直接结论:用不带选择器的.state() Mixin封装声明块,配合类名手动绑定,而非在Mixin里写&:hover或硬编码.is-disabled——这样才能复用、可测试、能SSR、兼容主题切换。

直接结论:用不带选择器的 .state() Mixin 封装声明块,配合类名手动绑定,而不是在 Mixin 里写 &:hover 或硬编码 .is-disabled —— 这样才能复用、可测试、能 SSR、兼容主题切换。
为什么不能把 &:hover 写进按钮 Mixin?
Less 每次调用 Mixin 都会原样展开全部内容。如果 .btn-variant() 里写了 &:hover { background: darken(@bg, 15%); },那每个调用它的地方(比如 .btn-primary、.btn-danger)都会生成一份重复的 :hover 规则,CSS 体积膨胀,且无法被 PurgeCSS 安全剔除。
更严重的是,伪类和真实状态类混用会导致行为与样式脱节:比如 JS 设置了 aria-disabled="true",但 CSS 只靠 :hover 做禁用反馈,用户悬停时仍触发动画,视觉欺骗性极强。
- 伪类(
:hover、:active)是交互瞬态,不可控、不可测、不支持 SSR - 状态类(
.is-disabled、.is-loading)是明确的布尔信号,可由 JS 精确控制、可写单元测试、服务端可直出 - 深色模式等媒体查询无法覆盖伪类,但可以轻松重写
.is-disabled的颜色值
.state() Mixin 怎么定义才真正可复用?
核心原则:只输出声明块,不碰选择器;参数按高频优先排序;默认值要合理,避免每次调用都填满。
.state(@opacity: 0.6, @cursor: not-allowed, @pointer: none, @color: #999) {
opacity: @opacity;
cursor: @cursor;
pointer-events: @pointer;
color: @color;
}
调用时才绑定具体语义类名:
.is-disabled { .state(); }
.is-loading { .state(0.8, wait, auto, #666); }
.is-active { .state(1, default, auto, #333); }
- 前缀不写死——项目用
.u-disabled或.btn--disabled都能无缝适配 - 需要额外结构(如
.is-loading::after加载图标)就另建.state-loading(),别塞进通用.state() - 参数顺序按实际使用频率排,
@opacity出现最多,放第一位;@color较少动,放末尾
禁用态颜色怎么自动推导,又不依赖 opacity?
用 Less 内置函数做色彩运算,而不是靠 opacity 蒙一层灰——前者保色相、可读性高,后者在深色背景下直接糊成一片。
例如在 .btn-variant() 中这样写:
.btn-variant(@bg: #007bff; @border: darken(@bg, 10%); @color: #fff) {
background-color: @bg;
border-color: @border;
color: @color;
&.is-disabled {
background-color: desaturate(lighten(@bg, 25%), 30%);
border-color: desaturate(lighten(@bg, 20%), 40%);
color: desaturate(lighten(@bg, 50%), 20%);
}
}
-
desaturate()降低饱和度,比单纯fadeout()更可控 -
lighten()提亮基础色再降饱和,确保深色背景上文字依然可读 - 所有计算都在编译期完成,产物是静态 CSS,无运行时开销
最易被忽略的一点:状态类必须和组件的 JS 控制逻辑对齐。比如 Vue 组件用 :class="{ 'is-disabled': disabled }",React 用 className={clsx('btn', { 'is-disabled': disabled })} —— 如果 Mixin 输出的是 &:disabled 或 &[disabled],那就和这套约定完全错位,后期维护成本陡增。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











