大家好,我是石小石!小册《油猴脚本实战指南》作者!
在 Vue3 的开发中,我们经常需要根据组件的 props
或状态动态控制样式,如封装如下所示的Tag组件:

常见的方式无非两种:动态类名 和 动态内联样式 。这两种方式简单易用,但在部分样式复杂的场景下会显得冗长、杂乱,影响代码可维护性。其实 <style>
标签中也可以使用变量,实现响应式样式。
常见的动态样式实现方式
动态类名
如果组件样式枚举较少,我们可以通过类名控制样式,如:
js
<template>
<div class="tag" :class="tagClassName">
{{ label }}
</div>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
type: {
type: String,
default: 'success', // 可选:'success' | 'error'
},
label: String
})
const tagClassName = computed(() => {
return {
success: 'tag-success',
error: 'tag-error',
}[props.type] || ''
})
</script>
<style scoped>
.tag-success {
color: #0c9b4a;
background-color: #daf7e1;
}
.tag-error {
color: #d72824;
background-color: #ffe6e5;
}
</style>
动态内联样式(:style
)
如果组件的颜色、尺寸、渐变等多个样式值由用户传入,这种场景使用动态内联样式绑定是一个更好的选择。
js
<template>
<div class="tag" :style="{ color, background }">
{{ label }}
</div>
</template>
<script setup>
const props = defineProps({
color: String,
background: String,
label: String
})
</script>
内联样式的弊端
对于一些简单的样式,上述两种方式是非常实用的。但当样式变得复杂时,这些方式就显得较为繁琐。
比如,我们需要动态设置一个过渡动画的 transition
时间,可能会这么写:
js
<template>
<div class="tag" :style="{ transition: `all ${transition}s linear` }">
{{ label }}
</div>
</template>
<script setup>
const props = defineProps({
transition: {
type: Number,
default: 0.3
},
label: String
})
</script>
如果再增加一个背景渐变的颜色,写法可能会变成这样:
js
<template>
<div class="tag" :style="{
transition: `all ${transition}s linear`,
background: `linear-gradient(45deg, ${startColor}, ${endColor})`
}">
{{ label }}
</div>
</template>
可以看到,随着样式变量的增多,style
对象会变得越来越臃肿,书写难受,不易阅读,维护也麻烦。
当然,我们也可以借助 computed
对样式对象进行封装,以简化模板中的代码:
js
const styleVars = computed(() => ({
transition: `all ${props.transition}s linear`,
background: `linear-gradient(45deg, ${props.startColor}, ${props.endColor})`
}))
但这种方式依然存在一个问题:样式定义不集中 。一些基础样式写在 <style>
标签中,而动态样式却散落在 <template>
或 <script>
中,样式逻辑分离,不利于统一管理。
其实,还有一种更优雅的做法:在 <style>
标签中直接使用响应式变量!
<style>
中使用响应式变量
CSS 中的 v-bind()
Vue3 新增了 v-bind()
功能,允许我们在 <style scoped>
中直接绑定 JS 变量,实现样式集中且响应式。
js
<template>
<p>hello</p>
</template>
<script setup>
import { ref } from 'vue'
const theme = ref({
color: 'red',
})
</script>
<style scoped>
p {
color: v-bind('theme.color');
}
</style>
底层原理是 Vue 在构建时,将 v-bind()
解析成一个哈希化的 CSS 自定义属性(如 --7cc3d2-color
),并将对应的变量值通过内联样式应用到组件根元素,确保响应式更新且避免样式冲突。
CSS 变量和 :style
动态绑定
尽管 v-bind()
方式简单直观,但当样式需求变复杂时,单纯依赖它会暴露一些不足:
- 多样式变量管理不便 :例如同时动态控制背景色、文字颜色、阴影颜色等多个变量时,单条
v-bind()
难以集中管理。 - 样式变量间有计算关系:例如阴影颜色需要基于主题色动态计算时,无法直接用纯 CSS 表达计算逻辑。
- 扩展性差 :复杂样式逻辑难以用单个
v-bind()
表达,维护成本高。
更灵活且推荐的做法是,利用 Vue 的响应式计算属性,集中管理多个 CSS 变量,并通过绑定 :style
将它们注入到元素内联样式:
js
<template>
<button class="btn" :style="styleVars">动态按钮</button>
</template>
<script setup>
import { ref, computed } from 'vue'
const themeColor = ref('#409EFF')
// 动态计算阴影颜色(举例,实际可用更精确的颜色处理)
const shadowColor = computed(() => {
return themeColor.value.replace('#', 'rgba(0,0,0,0.15)')
})
// 集中管理样式变量
const styleVars = computed(() => ({
'--btn-bg': themeColor.value,
'--btn-color': '#fff',
'--btn-shadow': shadowColor.value
}))
</script>
<style scoped>
.btn {
background-color: var(--btn-bg);
color: var(--btn-color);
box-shadow: 0 4px 10px var(--btn-shadow);
padding: 10px 20px;
border: none;
border-radius: 4px;
transition: background-color 0.3s ease;
}
</style>
总结
Vue3 的 <style scoped>
里提供了 v-bind()
,可以直接把 JS 变量绑定成 CSS 变量,样式管理更集中,更新也很方便。不过,如果有多个变量还要相互计算,用单纯的 v-bind()
就不太够了。这个时候,可以用 Vue 的计算属性集中生成一组 CSS 变量,再用 :style
绑定到元素上,这样既能保证样式的响应式,又能轻松处理复杂的计算逻辑,代码也更清晰好维护。