人人都会AI编程

15.2 样式解决方案

更新时间:2026-07-09

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,但要做好性能评估。

最后提醒:样式的混乱往往不是方案本身的问题,而是缺少约定。无论用哪种方案,在一开始就制定好命名规范、文件组织、全局与局部样式的边界,比纠结具体技术更重要。