Vue3里怎么用Sass?有哪些你必须知道的适配变化和高效用法?
你是不是刚从Vue2转过来,一写Vue3组件的Sass就遇到深度选择器报错?或者搭项目时忘了配置路径别名,每次写@/assets都提示找不到文件?甚至用了Vue3的<style scoped>和组合式API,不知道Sass变量能不能直接跨组件或者和setup里的响应式数据联动?这篇文章就用你日常开发最常遇到的问题串起来,把Vue3和Sass的搭配说透,从项目搭建到细节优化,全是能直接落地的干货。
先搞懂:Vue3项目里怎么装、怎么配Sass?
刚创建Vue3项目(不管是Vite还是Vue CLI)别急着写样式,先确认脚手架有没有自带预处理器支持——如果没有,安装步骤超级简单,但不同脚手架的配置细节还真不一样,踩过坑的人都懂。
Vue CLI 5.x/4.x怎么搞?
如果是用Vue CLI的默认预设(比如Vue3+JavaScript),通常是不带Sass的,得手动装开发依赖:
先选一个预处理器核心包就行,别两个都装,没必要,推荐用sass(也就是以前的dart-sass),因为它是官方维护、性能更好、支持最新特性的版本,node-sass已经停止更新很久了,踩坑率高。
然后运行npm install -D sass sass-loader或者yarn add -D sass sass-loader、pnpm add -D sass sass-loader,这三个包管理工具随便选一个你习惯的就行。
安装完之后,是不是以为要在vue.config.js里写一堆loader配置?其实不用,Vue CLI 4.5以上就自动处理了,只要在单文件组件的<style>标签里加个lang="scss"或者lang="sass"(后者是缩进语法,前者是和CSS更像的嵌套语法,推荐前者,新手友好、团队协作规范也统一)。
不过如果你想加路径别名,比如把@/assets/styles/variables.scss改成@styles/variables.scss,那得在vue.config.js里加一点点东西,还有,全局引入变量、mixin、函数这些不用每个组件都@import的配置,也是很多人问的,这个后面单独讲。
Vite 3.x/4.x/5.x怎么搞?
Vite的配置更简洁,因为它对预处理器有原生的优化支持,不需要像Webpack那样折腾loader。
第一步同样是装开发依赖:npm install -D sass(只用装dart-sass,Vite内置了对sass-loader的替代处理逻辑),包管理工具同上。
第二步也是在<style>标签加lang="scss",直接就能用。
那路径别名和全局变量这些呢?Vite的配置在vite.config.js里,路径别名的写法稍微有点变化,得用node的path模块拼接绝对路径,还得在tsconfig.json(如果是TS项目)或者jsconfig.json(JS项目)里加对应的配置,不然编辑器会提示找不到路径,但代码其实能跑——这个小细节很影响开发体验,别漏了。
避坑第一弹:Vue3的深度选择器写法变了!
这是Vue3和Sass搭配最常踩的第一个坑!以前Vue2用的/deep/、:v-deep,在Vue3的<style scoped>里要么失效,要么直接报错说不支持嵌套?
正确的Vue3深度选择器写法是什么?
有三个合法的写法,但推荐只用前两个,兼容性最好、也更规范:
第一个是deep(),这是Vue3推荐的标准写法,嵌套也没问题,比如你想修改组件里引用的第三方UI组件(比如Element Plus)的样式,就可以这么写:
// 这是你的父组件的<template>
<el-button class="custom-btn">自定义按钮</el-button>
// 这是你的父组件的<style scoped lang="scss">
.custom-btn {
:deep(.el-button__inner) {
color: #ff6b6b;
font-weight: 600;
}
}
第二个是v-deep(),这个是为了兼容旧版工具链保留的,功能和deep()完全一样,不过以后可能会逐渐被弃用,所以新项目尽量用第一个。
第三个是:v-deep,但这个写法在Vue3里不能嵌套在Sass的父选择器下面,必须单独放在一行,
// 错误写法!会报错或者失效
.custom-btn {
::v-deep .el-button__inner {
// ...
}
}
// 正确写法
.custom-btn {}
::v-deep .custom-btn .el-button__inner {
// ...
}
这种写法太麻烦了,直接忽略吧。
为什么要改深度选择器的写法?
其实是为了符合Web Components的CSS Scope规范,以后主流浏览器的Shadow DOM也能用类似的逻辑,Vue3这么改也是为了更贴合标准、减少未来的迁移成本。
高效开发第一步:全局引入变量、mixin、函数
你肯定不想每个组件都写一遍@import "@/assets/styles/variables.scss";吧?不仅麻烦,维护起来也超级累——万一哪天变量名改了,你得改几十上百个文件。
Vue CLI怎么全局引入?
用Vue CLI的话,在vue.config.js里加css.loaderOptions.sass.additionalData或者css.loaderOptions.scss.additionalData,注意,缩进语法的sass和嵌套语法的scss是分开配置的:
// vue.config.js
const { defineConfig } = require('@vue/cli-service');
module.exports = defineConfig({
css: {
loaderOptions: {
// 嵌套语法的scss用这个
scss: {
additionalData: `@import "@/assets/styles/variables.scss"; @import "@/assets/styles/mixins.scss";`
},
// 缩进语法的sass用这个(注意分号改成换行)
sass: {
additionalData: `@import "@/assets/styles/variables.sass"
@import "@/assets/styles/mixins.sass"`
}
}
}
});
这里要注意路径的写法,用Vue CLI的话,是默认指向src的,不用改,additionalData里的内容会被自动插入到每个scoped和非scoped的样式文件的最前面,所以里面不能写实际的样式(比如body { margin: 0; }),只能写变量、mixin、函数、@forward、@use这些不会生成实际CSS的东西——不然你的每个组件都会生成重复的全局样式,打包体积会变大很多。
Vite怎么全局引入?
Vite的全局引入不用插件吗?对,从Vite 2.6开始,就有了css.preprocessorOptions.scss.additionalData或者css.preprocessorOptions.sass.additionalData的配置,和Vue CLI类似,但路径别名的写法要注意,得用vite.config.js里配置好的别名:
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@styles': path.resolve(__dirname, './src/assets/styles')
}
},
css: {
preprocessorOptions: {
scss: {
additionalData: `@use "@styles/variables.scss" as *; @use "@styles/mixins.scss" as *;`
}
}
}
})
这里推荐用Sass的@use而不是@import,因为@use是Sass 3.0以后推出的模块化语法,作用域更清晰,不会出现变量名冲突的问题——比如你引入两个mixin文件,里面都有叫flex-center的mixin,用@import的话后面的会覆盖前面的,用@use的话可以给每个文件起别名,比如@use "@styles/mixins1.scss" as m1;,然后用m1.flex-center()。
进阶玩法:Vue3的响应式数据能不能联动Sass变量?
这个问题有点意思,以前Vue2是很难直接做到的,要么用CSS变量加style属性绑定,要么用计算属性动态生成class,但Vue3有了<script setup>、v-bind的样式绑定增强,结合CSS变量和Sass,就能玩出花来。
怎么实现?
核心思路是:把Vue3的响应式数据通过v-bind绑定到CSS变量上,然后在Sass里引用这个CSS变量,听起来有点绕,但写起来超级简单:
<template>
<div class="dynamic-box">
<p>点击按钮改变盒子大小和颜色</p>
<button @click="width += 20">变宽</button>
<button @click="height += 20">变高</button>
<button @click="colorIndex = (colorIndex + 1) % colors.length">换色</button>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const width = ref(200)
const height = ref(150)
const colors = ['#ff6b6b', '#4ecdc4', '#45b7d1', '#f9ca24']
const colorIndex = ref(0)
const currentColor = computed(() => colors[colorIndex.value])
</script>
<style scoped lang="scss">
.dynamic-box {
// 这里用v-bind绑定Vue的响应式数据到CSS变量
--box-width: v-bind(width + 'px');
--box-height: v-bind(height + 'px');
--box-color: v-bind(currentColor);
// Sass里直接引用CSS变量,还能做简单的计算!
width: var(--box-width);
height: var(--box-height);
background-color: var(--box-color);
border-radius: calc(var(--box-width) / 20);
padding: 20px;
color: #fff;
transition: all 0.3s ease;
}
</style>
你看,这个功能是不是很实用?比如做主题切换、动态生成图表样式、响应式布局微调这些场景,都能用得上。
这里的Sass和CSS变量怎么配合?
Sass的变量是编译时变量,也就是在打包的时候就把值替换成固定的了,不能动态改变;而CSS变量是运行时变量,能在浏览器里实时修改,所以两者的定位是不一样的:Sass变量用来存静态的、全局的样式参数(比如主色、间距、字体大小),CSS变量用来存动态的、和Vue响应式数据联动的参数,两者配合起来,既能享受到Sass的模块化、嵌套、mixin、函数等高效特性,又能实现Vue3的动态样式效果。
避坑第二弹:组合式API和<script setup>下的样式穿透有什么注意事项?
其实刚才讲的深度选择器已经覆盖了大部分情况,但还有个小细节要注意:如果你用了Vue3的<slot>插槽,插槽里的内容是属于父组件的,所以默认情况下<style scoped>里的样式不会影响插槽里的内容——除非你用刚才讲的深度选择器,但如果插槽里的内容是第三方UI组件的子组件,那还是得用deep()。
如果你用了<script setup>里的<teleport>,也就是把组件的内容传送到DOM树的其他位置(比如body下面),那<style scoped>里的样式对传出去的内容也是无效的,因为scoped的作用域是基于当前组件的根元素的,而传出去的内容不在根元素下面,这时候你得把传出去的内容的样式写成非scoped的,或者给传出去的内容加个全局唯一的class,然后在非scoped的样式里写。
高效开发第二步:用Sass的新特性简化Vue3的样式
刚才提到了Sass的@use模块化语法,其实还有很多Sass的新特性能大大简化Vue3的样式开发,这里挑几个最实用的讲:
第一个是@mixin结合@content,用来写响应式布局
响应式布局是每个前端开发者都要写的,用@mixin结合@content能把媒体查询封装起来,不用每次都写一堆重复的代码:
// mixins.scss
@use "variables.scss" as *;
@mixin responsive($breakpoint) {
@if map-has-key($breakpoints, $breakpoint) {
@media screen and (min-width: map-get($breakpoints, $breakpoint)) {
@content;
}
} @else {
@error "断点 #{$breakpoint} 不存在!请检查variables.scss里的$breakpoints";
}
}
// variables.scss
$breakpoints: (
'sm': 640px,
'md': 768px,
'lg': 1024px,
'xl': 1280px
);
然后在你的Vue3组件里直接用:
<style scoped lang="scss">
.container {
width: 100%;
padding: 0 16px;
@include responsive('md') {
padding: 0 32px;
}
@include responsive('lg') {
width: 960px;
margin: 0 auto;
}
}
</style>
是不是比每次写@media screen and (min-width: 768px)方便多了?而且还加了错误提示,防止你写错断点名。
第二个是@extend或者占位符选择器,用来复用样式
如果你有多个组件需要用同一段基础样式(比如按钮的基础样式、卡片的基础样式),可以用占位符选择器,这样不会生成实际的CSS,只有被@extend引用的地方才会生成:
// mixins.scss
%base-btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 8px 16px;
border-radius: 4px;
border: none;
cursor: pointer;
transition: all 0.2s ease;
&:hover {
opacity: 0.8;
}
&:active {
opacity: 0.6;
}
}
然后在你的Vue3组件里用:
<style scoped lang="scss">
.primary-btn {
@extend %base-btn;
background-color: #409eff;
color: #fff;
}
.secondary-btn {
@extend %base-btn;
background-color: #f5f7fa;
color: #606266;
border: 1px solid #dcdfe6;
}
</style>
这样打包出来的CSS会把.primary-btn和.secondary-btn的公共样式合并在一起,减少打包体积。
第三个是@function,用来计算样式值
比如你想把px转换成rem,或者计算颜色的透明度,都可以用@function:
// variables.scss
$base-font-size: 16px;
// mixins.scss
@use "variables.scss" as *;
@function px2rem($px) {
@if $px == 0 {
@return 0;
}
@return $px / $base-font-size * 1rem;
}
@function rgba-hex($hex, $alpha) {
$red: red($hex);
$green: green($hex);
$blue: blue($hex);
@return rgba($red, $green, $blue, $alpha);
}
然后在你的Vue3组件里用:
<style scoped lang="scss">
.text {
font-size: px2rem(18px);
color: rgba-hex(#409eff, 0.8);
line-height: px2rem(27px);
}
</style>
是不是很方便?而且如果以后你的项目要改基础字体大小,只需要改$base-font-size这一个值就行。
Vue3和Sass搭配的最佳实践
- 用dart-sass代替node-sass:官方维护、性能更好、支持最新特性,踩坑率低。
- 用
deep()作为深度选择器的标准写法:符合Web Components规范,兼容性好、嵌套也没问题。 - 全局引入变量、mixin、函数,不要每个组件都@import:用Vue CLI的
css.loaderOptions或者Vite的css.preprocessorOptions,减少重复代码、方便维护。 - 用Sass的
@use模块化语法代替@import:作用域更清晰,不会出现变量名冲突的问题。 - Sass变量和CSS变量配合使用:Sass变量存静态的全局参数,CSS变量存动态的和Vue响应式数据联动的参数。
- 用
@mixin结合@content封装响应式布局、用占位符选择器结合@extend复用基础样式、用@function计算样式值:大大提高开发效率、减少打包体积。
好了,以上就是Vue3和Sass搭配的所有核心内容,从项目搭建到避坑,再到高效开发和进阶玩法,全是能直接用到项目里的干货,如果你还有什么问题,欢迎在评论区留言交流。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网



