Vue3单文件组件

Vue 3 单文件组件完全指南

本文档从零开始讲解 Vue 3 的单文件组件(SFC,Single File Component):什么是 .vue 文件、 / / 各自写什么、多块 script 与 style、scoped 与深度选择器、预处理器与 src 引用等,配有大量示例,适合新手系统学习。


一、什么是单文件组件?

1.1 概念

单文件组件就是一个 .vue 文件对应一个 Vue 组件
这个文件里把结构(HTML)逻辑(JavaScript/TypeScript)样式(CSS) 写在一起,用三块标签区分:

  • :组件的模板(HTML + Vue 模板语法)。
  • :组件的逻辑(数据、方法、生命周期等)。
  • :组件的样式(只影响当前组件或全局,可加 scoped)。

这样一个功能就在一个文件里,便于维护和复用。

1.2 长什么样?

一个最小的单文件组件示例:

<!-- HelloWorld.vue -->
<template>
  <p>你好,{{ name }}!</p>
</template>

<script setup>
import { ref } from 'vue'
const name = ref('Vue 3')
</script>

<style scoped>
p {
  color: blue;
}
</style>
  • :只能有一个根级“容器”(Vue 3 可以是多个根节点,见下文)。
  • :用组合式 API,顶层变量自动暴露给模板。
  • :只影响本组件内的元素,不污染其它组件。

下面分别细说这三块。


二、:模板

2.1 只能有一个根,或多个根(Vue 3)

Vue 2: 里必须有且只有一个根元素,多写几个并列的会报错。

Vue 3:支持多个根节点(Fragment),例如:

<template>
  <header>头部</header>
  <main>主体</main>
  <footer>底部</footer>
</template>

若只有一个根,通常包在一个

里,方便加 class 或统一样式:

<template>
  <div class="page">
    <p>内容</p>
  </div>
</template>

2.2 里面能写什么?

  • HTML 标签
  • Vue 模板语法:双花括号 {{ }}v-ifv-forv-model@click 等。
  • 自定义组件:在 script 里 import 后,在模板里当标签用。

不能写多个顶层元素之外的非 HTML、非 Vue 的语法(例如不能直接写 )。

2.3 用 src 引用外部模板(少见)

若模板很长,可以放到单独文件里,用 src 引用:

<template src="./template.html"></template>

一般不常用,多数情况直接写在 里即可。


三、 与

3.1 两种写法

写法一:(推荐)

  • 顶层变量、函数、import 的组件会自动暴露给模板,不用 return
  • 只能有一个 (可以和普通 同时存在)。
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>

写法二:选项式 (export default)

  • datamethodsmounted 等选项。
  • 需要 export default { … }
<script>
export default {
  data() {
    return { count: 0 }
  },
  methods: {
    add() {
      this.count++
    }
  }
}
</script>

新手若从组合式 API 学起,用 即可。

3.2 同时有 和

可以同时存在:

  • :写主要逻辑,自动暴露给模板。
  • 普通 :一般用来写 export default不依赖 setup 的配置,例如组件名inheritAttrs 等。
<script>
export default {
  name: 'MyComponent',
  inheritAttrs: false
}
</script>

<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
  • 同一文件里 都会执行;setup 先执行。
  • 组件名在开发工具、keep-alive 等场景会用到。

3.3 lang=”ts”:用 TypeScript

若项目配了 TypeScript,可给 script 加 lang=”ts”

<script setup lang="ts">
import { ref } from 'vue'
const count = ref<number>(0)
</script>

四、:样式

4.1 普通 :全局

不加 scoped 时, 里的选择器是全局的,会影响整个项目里匹配到的元素,容易误伤其它组件。
一般只在刻意写全局样式(如重置、主题变量)时用。

<style>
.global-title {
  font-size: 24px;
}
</style>

4.2 scoped:只影响当前组件

加上 scoped 后,Vue 会给当前组件的根元素及其子元素加一个唯一属性(如 data-v-xxxxx),选择器会变成“只匹配带这个属性的元素”,从而只作用于本组件

<template>
  <div class="box">
    <p class="text">只有本组件的 p 会变红</p>
  </div>
</template>

<style scoped>
.box {
  padding: 16px;
}
.text {
  color: red;
}
</style>
  • 其它组件里若有 .text 类,不会受这段样式影响。
  • 子组件根元素会同时带上父的 scoped 属性和自己的,所以父的 scoped 选择器可以选中子组件根元素;但选不中子组件内部的元素,除非用深度选择器。

4.3 多个 块

可以写多个 ,有的 scoped,有的不写(全局):

<style scoped>
.component-class {
  color: blue;
}
</style>

<style>
body {
  margin: 0;
}
</style>
  • 一个组件里可以既有“只给自己用的 scoped”,又有“给全局用的”样式。

4.4 深度选择器:影响子组件内部

若要在父组件的 scoped 样式里,选中子组件内部的元素,要用深度选择器
Vue 3 推荐用 :deep()

<style scoped>
.parent :deep(.child-inner) {
  color: red;
}
</style>
  • 这样 .child-inner 即使在子组件内部,也会被这段样式命中。
  • 其它写法如 ::v-deep/deep/ 在 Vue 3 中请用 :deep() 替代。

4.5 在 style 里用响应式数据(v-bind)

Vue 3.2+ 支持在 里用 v-bind(变量),把 script 里的响应式数据绑到 CSS 上:

<template>
  <div class="box">颜色随数据变</div>
</template>

<script setup>
import { ref } from 'vue'
const themeColor = ref('blue')
</script>

<style scoped>
.box {
  background-color: v-bind(themeColor);
}
</style>
  • themeColor 变化时,背景色会同步更新。
  • 适合主题色、动态尺寸等。

4.6 预处理器:lang=”scss” / “less”

若项目装了 sassless,可以给 style 加 lang=”scss”lang=”less”

<style scoped lang="scss">
$color: blue;
.box {
  padding: 16px;
  .inner {
    color: $color;
  }
}
</style>

需要先安装对应依赖(如 sassless)。

4.7 用 src 引用外部样式文件

样式也可以放到单独文件,用 src 引入:

<style scoped src="./styles.css"></style>

五、文件命名与组件名

5.1 文件命名

  • 推荐PascalCasekebab-case,如 UserCard.vueuser-card.vue
  • 一个 .vue 文件就是一个组件,文件名即“组件文件名”,在模板里用时可写成 UserCarduser-card

5.2 组件名(name)

  • 里不能直接配置 name,若需要可在普通 export default { name: ‘MyComponent’ }
  • 不写 name 时,Vue 会按文件名推断;keep-aliveinclude/exclude、开发工具里会用到 name

六、完整示例:一个带 template / script / style 的组件

<!-- UserCard.vue -->
<template>
  <div class="user-card">
    <img :src="user.avatar" :alt="user.name" class="avatar" />
    <div class="info">
      <h3>{{ user.name }}</h3>
      <p>{{ user.bio }}</p>
    </div>
  </div>
</template>

<script setup>
import { computed } from 'vue'
const props = defineProps({
  user: {
    type: Object,
    required: true
  }
})
const displayName = computed(() => props.user.name || '匿名')
</script>

<style scoped>
.user-card {
  display: flex;
  gap: 12px;
  padding: 16px;
  border: 1px solid #eee;
  border-radius: 8px;
}
.avatar {
  width: 48px;
  height: 48px;
  border-radius: 50%;
}
.info h3 {
  margin: 0 0 4px 0;
  font-size: 16px;
}
.info p {
  margin: 0;
  color: #666;
  font-size: 14px;
}
</style>
  • template:一个根 div,内部用 Vue 语法绑定 user
  • script setupdefineProps 接收 usercomputed 做展示用名字。
  • style scoped:只影响 .user-card 及其子元素。

七、在别的组件里使用单文件组件

7.1 引入并使用

父组件import 这个 .vue 文件,在 里 import 后,模板里可直接当标签用( 会自动暴露 import 的组件):

<!-- App.vue -->
<template>
  <div>
    <UserCard :user="user" />
  </div>
</template>

<script setup>
import UserCard from './components/UserCard.vue'
import { ref } from 'vue'
const user = ref({
  name: '小明',
  avatar: '/avatar.jpg',
  bio: '前端开发'
})
</script>
  • UserCard 来自 import,对应 UserCard.vue
  • 模板里写 均可。

7.2 路径与别名

  • 相对路径:‘./components/UserCard.vue’‘../components/UserCard.vue’
  • 若项目配了路径别名(如 @ 表示 src),可写 @/components/UserCard.vue

八、单文件组件结构小结

作用 说明
结构 一个根或多个根(Vue 3),可写 HTML + Vue 语法
逻辑 选项式:export default { data, methods, … }
逻辑 组合式,顶层自动暴露,推荐
样式 不加 scoped 为全局
样式 只影响本组件,子组件内部需 :deep()
样式 使用预处理器

九、常见问题与注意点

9.1 template 里只能有一个“顶层”或多个根

不能写多个不包在一起的顶层元素再在外面包一层别的(逻辑上仍是“一个根”即可)。Vue 3 多根时,每个根是平级的。

9.2 scoped 只加在“当前组件”的 DOM 上

子组件根元素会带父的 data-v-xxx,所以父的 scoped 能选中子组件根;子组件内部的类要用 :deep() 才能从父组件样式里命中。

9.3 多个 style 时 scoped 和全局别搞混

scoped 的块只影响本组件;不写 scoped 的会全局生效,类名尽量用不会冲突的(如加组件名前缀)。

9.4 script 与 script setup 同时存在时

普通 export default 的选项(如 name)和 的变量/函数会合并:模板用 setup 暴露的,组件配置用 export default 的。


十、学习建议

  1. 先熟练一个 .vue 文件template + script setup + style scoped 的三块结构。
  2. 模板里只写一个根多个平级根;样式尽量 scoped,需要改子组件内部时用 :deep()
  3. 需要 nameinheritAttrs 等再加普通 ;需要 TypeScript 再加 lang=”ts”
  4. 组件拆分:一个功能或一块 UI 一个 .vue 文件,便于复用和维护。

把本文档里的 UserCard.vueApp.vue 在项目里敲一遍、改一改,会掌握得更牢。更多组件通信、插槽等见《Vue3组件.md》《Vue3插槽.md》。祝你学习顺利。

发表评论