Vue 不限制你使用任何 CSS 方案,它的单文件组件(.vue)天然支持多种写法,你只需要在 <style> 标签里书写样式。但“能写”和“写得规范、好维护”是两回事。这一节梳理 Vue 项目中几种主流样式方案,以及它们各自最适合的场景。
原生 CSS / SCSS / Less + scoped:最经典的组合
这是 Vue 组件样式的默认推荐模式:在 <style> 标签上加上 scoped 属性,Vue 会在编译时为组件内的每个元素添加一个唯一的 data-v-xxxx 属性(如 data-v-f3f3eg),同时重写你的 CSS 选择器,确保样式只作用于当前组件,不会泄漏到子组件或全局。
<template>
<div class="card">
<h2>{{ title }}</h2>
</div>
</template>
<style scoped>
.card {
border: 1px solid #eee;
padding: 1rem;
}
h2 {
color: #333;
}
</style>
编译后生成的 CSS 类似于:
.card[data-v-f3f3eg] {
border: 1px solid #eee;
padding: 1rem;
}
h2[data-v-f3f3eg] {
color: #333;
}
scoped 的优势与局限:
- 优势:上手零成本,无需额外配置;样式隔离清晰,不同组件可以放心使用相同的 class 名称而互不干扰。
- 局限:因为通过属性选择器提升了优先级,如果需要从父组件覆写子组件的内部样式,就需要用到
:deep()深度选择器(Vue 3 中取代了 Vue 2 的>>>和/deep/):
.parent :deep(.child-class) {
color: red;
}
这种“穿透”操作虽然能解决问题,但过度使用会破坏封装性,通常建议只在不可避免时使用(例如覆盖第三方组件的局部样式)。
SCSS / Less 集成:只需在 <style> 标签上声明 lang="scss" 或 lang="less",并在项目中安装对应的预处理器,Vite 或 Vue CLI 会直接支持。Sass/SCSS 的变量、嵌套、mixin 等能力让大型项目样式组织更清晰。
实用建议:中小型项目或组件库内部,scoped + SCSS 组合基本够用。但注意不要滥用深度选择器和全局样式混写,否则维护会逐渐失控。
CSS Modules:另一种样式隔离思路
与 scoped 的编译时属性挂载不同,CSS Modules 通过类名映射来实现隔离。你需要给 <style> 添加 module 属性,然后在模板中通过 $style 对象引用类名。
<template>
<div :class="$style.card">
<h2 :class="$style.title">{{ title }}</h2>
</div>
</template>
<style module>
.card {
border: 1px solid #eee;
padding: 1rem;
}
.title {
color: #333;
}
</style>
编译后,.card 可能变成 _card_1a2b3_4 这样的唯一哈希,你通过 $style.card 访问它。这种方案的优势在于:
- 完全避免了选择器冲突,即使多个组件中都有
.card,最终类名也会完全不同。 - 因为是通过 JavaScript 对象引用,可以在
<script>中动态组合样式类名,与 TypeScript 配合良好。
缺点:
- 必须通过
$style引用,写法上不如直接写 class 名字直观,部分开发者觉得啰嗦。 - 全局样式(如重置浏览器默认样式)仍然需要单独的全局 CSS 文件,与局部模块样式分属两个体系。
何时选择 CSS Modules:如果你来自 React 生态,熟悉 CSS Modules 的工作方式;或者你的团队更偏好“类名即唯一标识”的隔离哲学而非属性选择器;或者需要频繁在 JS 中动态拼装类名时,它是一个坚实的选择。
原子化 CSS:Tailwind CSS / UnoCSS
原子化 CSS 的核心思想是“一个工具类负责一个样式属性”,通过组合大量微小类来构建界面,基本不写或极少写自定义类。Vue 里集成 Tailwind CSS 非常成熟,而 UnoCSS 是更轻量、更灵活的选择。
Tailwind CSS 用法示例:
<template>
<div class="border border-gray-200 p-4 rounded-lg">
<h2 class="text-lg font-semibold text-gray-800">{{ title }}</h2>
<p class="mt-2 text-sm text-gray-600">{{ desc }}</p>
</div>
</template>
你完全在模板中语义化地声明样式,无需离开 HTML 上下文。它的优势:
- 开发速度极快:不需要在模板和样式文件之间频繁跳转,所见即所得地调样式。
- 体积自动优化:Tailwind 在生产构建时会清除未使用的类(PurgeCSS),最终 CSS 通常只有几 KB。
- 设计约束一致:预定义的颜色、间距、阴影值强迫团队使用统一的设计语言,减少随意性。
UnoCSS:比 Tailwind 更快、更极简。它在 Vite 上运行,按需生成 CSS,支持自定义规则和属性模式(如 text-red 自动生成 color: red)。如果你追求极致性能或需要深度定制,它是一个新选择。
原子化 CSS 的争议:很多开发者认为“类名堆砌看着脏”,但时间一长,习惯了便觉得“类名就是样式文档”。我们建议在团队内部做小范围试用,如果多数人接受它的哲学,则可以在项目中采用,否则避免强制推行。
CSS-in-JS:Vue 生态的适配
React 社区流行的 CSS-in-JS(styled-components、Emotion 等)在 Vue 中也有类似方案,比如 vue3-styled-components 或 @vue-styled-components/core。它让你在 JavaScript 中定义样式组件:
import styled from 'vue3-styled-components'
const StyledTitle = styled.h2`
color: #333;
font-size: 1.2rem;
`
然后在模板中使用 <StyledTitle>内容</StyledTitle>。
优点:利用 JS 的动态能力(props 动态样式)、样式与组件强绑定、无类名冲突。
在 Vue 中使用的实际考量:
- Vue 的 SFC(单文件组件)本身已经提供了模板、脚本、样式的强关联,CSS Modules 也解决了隔离问题,多数场景不需要 CSS-in-JS。
- CSS-in-JS 会增加运行时开销(生成样式和类名),对性能敏感场景不利。
- TypeScript 类型推导在 CSS-in-JS 上更完整。
结论:除非你是从 React 迁移过来的团队,并且强烈依赖这种模式,否则我们不推荐在 Vue 中首选 CSS-in-JS。Vue 生态的 scoped 和 CSS Modules 已经足够好。
工程化整合与最佳实践
不管你选择哪种方案,都推荐将全局重置样式(如 normalize.css 或你自己的 reset)通过一个单独的全局样式文件引入,并在 main.js 中全局加载。对于组件库的主题定制,利用 SCSS 变量或 CSS 变量(--primary-color)都可以做到方便修改。
选择策略:
- 快速原型/小项目:直接 scoped + CSS 或 SCSS。
- 中大型团队追求一致性:Tailwind CSS 或 UnoCSS,结合设计系统。
- 需要极高的样式封装或 JS 动态样式:CSS Modules。
- 从 React 迁移且保持统一技术栈:可以考虑 CSS-in-JS,但要做好性能评估。
最后提醒:样式的混乱往往不是方案本身的问题,而是缺少约定。无论用哪种方案,在一开始就制定好命名规范、文件组织、全局与局部样式的边界,比纠结具体技术更重要。