@use
在 《动态设置网站的主题,顺便狠狠地介绍相关 sass 用法》一文中,定义比主题色稍淡的颜色变量 --primary-color-light
时,我使用到了 mix()
函数,并且说到该函数来自于 sass 为我们提供的内置模块 sass:color。mix()
其实是color.mix()
的一个全局别名(global aliases),sass 团队并不建议我们直接使用那些定义在内置模块中的函数的全局别名,并且最终会弃用它们:
The Sass team discourages their use and will eventually deprecate them, but for now they remain available for compatibility with older Sass versions and with LibSass.
按照推荐的用法,我们应该使用 @use
指令导入 sass:color,然后在使用时通过 color.mix()
来使用:
scss
@use 'sass:color';
--primary-color-light: #{color.mix(#fff, $color, 10%)};
但是直接这样做的话,在运行项目时可能会遇到如下报错:
解决报错,需要在 nuxt.config.js 中将使用了 @use
的文件 theme.scss,调整到 styleResources.scss
数组的第一位:
有的小伙伴可能要问了:过去,在 sass 中导入其它样式文件通常使用的不都是 @import
吗?这个 @use
是什么?color.mix()
的 color
又是哪里来的?接下来,我们先针对这些疑问,对 @use
做进一步的介绍。
Dart 的不同实现
从 Dart Sass 1.23.0 开始,可以使用 @use
指令来导入样式表,并且这些使用 @use
指令导入的样式表会被认为是模块,默认以文件名作为该模块的命名空间。之所以说是从 Dart Sass 1.23.0 开始可以使用 @use
,是因为 dart 有几种不同的实现 ------ 可以分为 Dart Sass,LibSass 和 Ruby Sass。
Ruby Sass 是 sass 的最初实现,但是现在它已经不再受到支持。LibSass 是 sass 在 c/c++ 中的一个实现,要在本地运行,还需要一个 LibSass 的封装,比如 js 封装的 sass.js,node 封装的 node-sass 等,但现在 LibSass 也被弃用了,新项目都应该使用 Dart Sass。
@use
与 @import
的区别
多次导入同一文件
@use
同 @import
一样,也可以导入 .scss 或 .css 文件。比如我们有 _a.scss:
scss
$size: 20px;
.a {
font-size: $size;
}
和 b.css:
css
.container {
color: red;
}
如果我们在 index.scss 中使用 @import
导入 _a.scss 与 b.css,并且还都各自导入 2 次:
scss
@import './a';
@import './a';
@import './b';
@import './b';
编译 index.scss 生成的 index.css 结果如下,可以看到导入 2 遍的后果就是文件的内容也被生成 2 遍:
css
.a {
font-size: 20px;
}
.a {
font-size: 20px;
}
.container {
color: red;
}
.container {
color: red;
}
而如果使用的是 @use
:
scss
@use './a';
@use './a';
@use './b';
@use './b';
则在编译时会直接报错:
当我们 @use './a';
导入 _a.scss 时,sass 会默认以文件名 "a" 作为命名空间(namespace),所以重复的导入会引起报错。解决的办是通过 as
给导入的文件,也就是模块指定别名:
scss
@use './a' as a1;
@use './a' as a2;
@use './b' as b1;
@use './b' as b2;
这样成功编译后生成的 index.css 文件中,重复的内容只会生成 1 次:
css
.a {
font-size: 20px;
}
.container {
color: red;
}
使用导入文件中的变量、函数或混入
对于导入的文件中的变量等内容,在使用时,不同于 @import
时的直接使用,使用 @use
导入时需要在变量等内容前加上导入文件(模块)的命名空间:
scss
@use './a';
.box {
height: a.$size;
}
问号比较多的小伙伴读到这可能又有疑问了:这么写不是更麻烦了,本来直接可以用的东西现在非得多写几个字,有没有什么简便的方法呢?答案是使用 *
来取消别名:
scss
@use './a' as *;
.box {
height: $size;
}
查看 element-plus 的源码,会发现它们在定义样式文件时,很多地方都是这么干的:
私有成员
使用 @use
对比 @import
还有个好处在于可以通过给变量等名称的前面添加 _
或 -
来定义私有成员:
scss
$size: 20px;
$_color: blue; // 我是私有的
.a {
font-size: $size;
color: $_color; // 内部可用
}
这样在被外部文件导入时,外部文件是用不了 $_color
的:
scss
@use './a' as *;
.box {
height: $size;
color: $_color; // 报错 Private members can't be accessed from outside their modules.
}
更改变量的默认值
如果某个文件的变量原本有默认值:
scss
$size: 20px !default;
.a {
font-size: $size;
}
在使用 @use
导入时怎么重新赋值呢?方法是通过对导入语句添加 with()
:
scss
@use './a' with($size: 24px);
.box {
height: a.$size;
}
编译后的 css 文件如下:
css
.a {
font-size: 24px;
}
.box {
height: 24px;
}
使用 @use
导入文件的一个问题
假设现在有 3 个 sass 文件:在 _b.scss 中定义了变量 $color: yellow;
;在 _a.scss 中使用 @import './b';
导入了 _b.scss;最后又在 index.scss 里 @import './a';
导入了 _a.scss,那么在 index.scss 中,是可以直接使用变量 $color
的:
编译 index.scss 得到的 index.css 结果为:
css
.box {
color: yellow;
}
但是如果我们将导入文件的指令由 @import
替换成上文中介绍的 @use
后:
在 index.scss 中就无法使用 $color
了,无论是通过 a.$color
还是 b.$color
都会报错。为了解决这个问题,就需要用到 @forward
指令。
@forward
我们可以将 _a.scss 中的 @use './b';
改为 @forward './b';
:
在 _a.scss 中使用 @forward './b';
的效果就好像是在 _a.scss 中定义了 $color: yellow;
一般,所以在 index.scss 里使用 $color
时,需要添加上 _a.scss 的命名空间,即 a.$color
。
@forward
的作用
@forward
导入样式文件时,类似于使用 @use
,也会将被导入文件中的内容导入到当前文件中。比如 _b.scss 中除了变量外还定义了样式:
scss
$color: yellow;
.b {
font-size: 12px;
}
我们再把 _a.scss 改为 a.scss 让该文件也被单独编译成 css 文件,可以看到编译结果 a.css 中的内容如下:
css
.b {
font-size: 12px;
}
但是在 a.scss 中,是不能直接使用 _b.scss 里的变量 $color
的。如果要使用,还需要在 a.scss 中写上 @use './b';
:
scss
@forward './b';
@use './b';
.a {
color: b.$color;
}
读到这里可能有点懵,我再梳理一下,即在 a.scss 中 @forward './b';
的作用有 2 点:
- 在 a.scss 中导入 _b.scss 里定义的样式;
- 让通过
@use './a';
导入了 a.scss 的 index.scss,可以使用 _b.scss 里定义的变量、函数和混入。但是 a.scss 本身不能使用 b.scss 里定义的变量、函数和混入,要使用还得在 a.scss 中写上@use './b';
。
另外注意 2 点:
- 在 a.scss 中同时
@forward './b';
和@use './b';
只会加载一次 _b.scss 模块,而不会有重复的导入; - 最好先
@forward './b';
再@use './b';
。在 sass 文档中有这样一句话:
A stylesheet`s @use rules must come before any rules other than @forward, including style rules.
其中的缘由会在文末说明。
控制可见性
使用 @forward
还可以控制哪些变量、函数或混入是可以被其它文件使用的。比如在 _b.scss 中定义了 2 个变量:
scss
$color1: yellow;
$color2: red;
在 _a.scss 中 @forward './b'
时就可以跟上 show
或 hide
来控制 index.scss 中可以使用的变量。比如我选择让 $color1
可见:
scss
@forward './b' show $color1;
那么在 index.scss 中就只能使用 a.$color1
:
scss
@use './a';
.box {
color: a.$color1;
}
如果使用 a.$color2
就会报错:
如果要展示或隐藏多个内容,可以使用 ,
分割:
scss
@forward './b' show $color1, $color2;
添加前缀
现在假设有 4 个文件,其中 _b.scss 与 _c.scss 都定义了变量 $color
,并且都在 _a.scss 通过 @forward
导入:
此时,在 index.scss 中 @use './a';
后使用 a.$color
,就会报错:
解决办法是在 @forward
的时候使用 as
定义前缀:
scss
@forward './b' as b-*;
@forward './c' as c-*;
使用时,就在变量名前添加上前缀:
scss
@use './a';
.box {
color: a.$b-color;
}
如果同时需要控制可见性,则书写顺序如下,注意 show
或 hide
的值也要加上前缀:
scss
@forward './b' as b-* show $b-color;
更改默认值
阅读 element plus 文档,在关于如何自定义主题的内容中,如果选择使用通过 scss 变量来更换主题色时,有如下一段使用 @forward
的示例:
在 @forward "<url>"
后还用到了 with
,其作用在于重新赋值 url 指向的文件中,被 !default
修饰的变量。比如给 _b.scss 中的 $color
添加上 !default
:
scss
$color: yellow !default;
那么在 _a.scss 中 @forward
时就可以通过 with
重新赋值,注意这里 with
内的 $color
并没有给 color
添加前缀 b-
:
scss
@forward './b' as b-* show $b-color with (
$color: green
);
这样在 index.scss 里使用:
scss
@use './a';
.box {
color: a.$b-color;
}
编译得到的 index.css 结果如下:
css
.box {
color: green;
}
如果要在 index.scss 中 @use './a'
时重新赋值 _b.scss 中的 $color
,则需要在 index.scss 中的 @use './a'
后使用 with
,并且此时是需要添加上前缀的,即 $b-color
:
scss
@use './a' with (
$b-color: purple
);
然后在 _a.scss 中的 with
里的 $color: green
也要添加上 !default
:
scss
@forward './b' as b-* with (
$color: green !default
);
@forward
与 @use
的先后顺序
在前文中介绍 @forward
的作用时,我说过当使用 @forward
与 @use
加载同一个文件时,最好先 @forward
,其中的原因就在于如果已经使用了 @use
加载某一文件,那么就不能再在之后的 @forward
时使用 with
重新赋值变量了。比如下面的写法:
scss
// 错误写法
@use './b';
@forward './b' as b-* with (
$color: green
);
.a {
color: b.$color; // 这里不要使用前缀写成 b.$b-color
}
就会有如下报错:
正确的写法是先 @forward
再 @use
:
scss
@forward './b' as b-* with (
$color: green
);
@use './b';
.a {
color: b.$color;
}