一、嵌套
为什么需要嵌套
在原生 CSS 中,书写后代选择器需要重复父级选择器:
1 2 3 4
| .nav { ... } .nav .nav-item { ... } .nav .nav-item .nav-link { ... } .nav .nav-item .nav-link:hover { ... }
|
Sass 的嵌套规则允许将子选择器写在父选择器的花括号内,直接对应 HTML 的层级结构:
1 2 3 4 5 6 7
| .nav { .nav-item { .nav-link { &:hover { ... } } } }
|
& — 父选择器引用
& 代表当前嵌套层级的外层选择器,用于生成复合选择器或伪类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| .btn { padding: 8px 16px;
&:hover { background: darken($bg, 10%); }
&.active { border-color: blue; }
&--large { font-size: 18px; }
&-icon { margin-right: 4px; } }
|
编译结果:
1 2 3 4 5
| .btn { padding: 8px 16px; } .btn:hover { background: ...; } .btn.active { border-color: blue; } .btn--large { font-size: 18px; } .btn-icon { margin-right: 4px; }
|
& 在嵌套中的特殊用法
1 2 3 4 5
| .card { .dark & { background: #222; } &.dark { background: #222; } }
|
属性嵌套
相同命名空间的属性可以嵌套书写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| .card { font-family: sans-serif; font-size: 14px; font-weight: bold; }
.card { font: { family: sans-serif; size: 14px; weight: bold; } }
|
1 2 3 4 5 6 7 8 9 10
| .card { border: 1px solid #ccc { left: none; bottom: { width: 3px; color: blue; } } }
|
嵌套的陷阱
过度嵌套导致选择器过深
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| .aside { .widget { .sidebar { .list { .item { a { color: blue; } } } } } }
|
建议:嵌套不超过 3 层。如果需要很深的选择器,重新审视 HTML 结构或使用 BEM 命名。
避免生成无意义的复合选择器
1 2 3 4 5 6 7 8 9
| .container { margin: 0 auto; .container-inner { width: 100%; } }
.container { margin: 0 auto; } .container-inner { width: 100%; }
|
@at-root
@at-root 将内部样式提升到文档根级别,脱离当前嵌套层级:
1 2 3 4 5 6 7 8 9 10 11
| .block { color: red;
@at-root { .block-wrapper { padding: 10px; } }
@at-root .block-outside { border: 1px solid; } }
|
编译结果:
1 2 3
| .block { color: red; } .block-wrapper { padding: 10px; } .block-outside { border: 1px solid; }
|
典型用途:配合 BEM 生成 modifier
1 2 3 4 5 6 7 8 9 10 11
| .card { background: white;
&__title { font-size: 16px; }
@at-root #{&}--dark { background: #333; color: white; } }
|
二、模块化
@import 已被弃用
1 2 3
| @import "variables"; @import "mixins";
|
@import 的问题:
- 全局命名冲突——所有变量和 mixin 都暴露在全局作用域
- 重复导入——多次
@import 同一文件导致重复输出 - 无私有成员——无法控制哪些内容对外暴露
@use — 模块化方案(推荐)
1 2 3
| $primary: #409eff !default; $danger: #f56c6c !default;
|
1 2 3 4 5 6 7 8 9
| @use 'variables'; @use 'variables' as v; @use 'variables' as *;
.button { color: variables.$primary; color: v.$primary; }
|
使用 @use 时,变量必须通过命名空间访问,避免了全局污染。
私有成员
以下划线 _ 或 - 开头的变量/mixin/函数视为私有:
1 2 3
| $-private-var: 10px; $public-var: 20px;
|
@forward — 转发/聚合导出
@forward 把一个模块的成员”透传”出去,方便组织入口文件:
1 2 3 4 5 6 7 8 9 10
| @mixin flex-center { ... } @mixin text-ellipsis { ... }
$primary: #409eff;
@forward 'variables'; @forward 'mixins';
|
1 2 3 4 5 6 7
| @use 'theme' as t;
.button { color: t.$primary; @include t.flex-center; }
|
使用 @forward 的成员控制
1 2 3 4 5 6 7 8 9 10 11 12
| @forward 'variables' hide $-internal, $temp;
@forward 'variables' show $primary, $danger;
@forward 'variables' as theme-*;
@use 'theme' as t;
|
内置模块
Dart Sass 提供了 7 个内置模块,通过 @use 导入使用(不再全局可用):
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @use 'sass:math'; @use 'sass:color'; @use 'sass:list'; @use 'sass:map'; @use 'sass:string'; @use 'sass:selector'; @use 'sass:meta';
$result: math.div(10, 3); $darker: color.adjust(#409eff, $lightness: -10%); $second: list.nth((a b c), 2); $exists: map.has-key($map, 'key'); $upper: string.to-upper-case('hello');
|
历史背景:在旧版 Sass 中,lighten()、darken()、rgba() 等函数是全局可用的。从 Dart Sass 开始,这些函数被移入 sass:color 模块。虽然为了兼容性全局函数仍可使用,但建议新代码改用模块化语法。
使用 @use 避免重复编译
对于 @mixin 和 @function 文件,使用 @use 而不是 @import 确保它们只被编译一次,并且不污染全局作用域。
三、Partials(局部文件)
以下划线 _ 开头的 SCSS 文件称为 partial,Sass 编译器不会将其编译为独立的 CSS 文件,仅在 @use 时按需加载。
1 2 3 4 5
| styles/ ├── _variables.scss ← partial(不会输出 _variables.css) ├── _mixins.scss ← partial ├── _reset.scss ← partial └── main.scss ← 入口文件,编译为 main.css
|
在入口文件引入:
1 2 3 4
| @use 'variables'; @use 'mixins'; @use 'reset';
|
@use 'variables' 会自动查找 _variables.scss,不需要加下划线和后缀。
四、实战示例:完整的主题模块化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| $prefix: 'el' !default;
@use 'config'; @use 'sass:map';
$colors: ( primary: #409eff, success: #67c23a, ) !default;
$-internal-gray: #ccc;
@use 'variables' as v;
@mixin button-theme($type) { background: map.get(v.$colors, $type); border: none; color: white; }
@forward 'config'; @forward 'variables'; @forward 'mixins';
|
1 2 3 4 5 6
| @use 'theme' as t;
.#{t.$prefix}-button { @include t.button-theme(primary); }
|