Vue3 创建单文件组件(SFC)(千字长文)

Vue3 创建单文件组件(SFC):从零开始掌握组件化开发

在现代前端开发中,组件化已成为构建复杂应用的核心思想。Vue 3 作为当前主流的前端框架之一,其单文件组件(Single File Component,简称 SFC)设计正是这一理念的完美体现。它将模板、逻辑和样式封装在一个文件中,让代码更清晰、更易维护。对于初学者来说,掌握 Vue3 创建单文件组件(SFC) 是迈向高效开发的第一步。

想象一下,你正在搭建一座积木城堡。如果每块积木都独立存在,你很难快速搭建出结构复杂的建筑。但如果你能将一块块积木组合成“模块”——比如一个塔楼、一座桥梁,再把它们拼在一起,整个城堡就变得井然有序。Vue 的 SFC 正是这样的“模块”,它把 HTML 模板、JavaScript 逻辑和 CSS 样式三者统一在一个 .vue 文件中,让开发更像搭积木。

SFC 的基本结构:一个组件的“完整身体”

每个 Vue3 的单文件组件都遵循一个固定的结构,它包含三个核心部分:<template><script><style>。这三个部分就像人体的三个主要系统——骨架(模板)、神经系统(逻辑)、皮肤(样式)。

<!-- App.vue -->
<template>
  <!-- 模板部分:定义组件的结构和视图 -->
  <div class="app">
    <h1>{{ message }}</h1>
    <button @click="changeMessage">点击切换</button>
  </div>
</template>

<script setup>
// 脚本部分:定义组件的行为和数据
// setup 是 Vue 3 的新语法,替代了原来的 options API
// 它让逻辑更简洁,也更接近函数式编程风格

import { ref } from 'vue'

// 使用 ref 定义响应式数据
const message = ref('欢迎来到 Vue3 世界!')

// 定义方法
function changeMessage() {
  message.value = message.value === '欢迎来到 Vue3 世界!' 
    ? '你已成功切换!' 
    : '欢迎来到 Vue3 世界!'
}
</script>

<style scoped>
/* 样式部分:定义组件的外观 */
/* scoped 表示样式只作用于当前组件,避免全局污染 */

.app {
  text-align: center;
  padding: 50px;
  font-family: 'Arial', sans-serif;
  background-color: #f0f8ff;
  border-radius: 10px;
  max-width: 500px;
  margin: 0 auto;
}

button {
  margin-top: 20px;
  padding: 10px 20px;
  font-size: 16px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
}

button:hover {
  background-color: #0056b3;
}
</style>

💡 注释说明

  • <template> 中的 {{ message }} 是数据绑定语法,Vue 会自动将 message 的值渲染到页面上。
  • @click 是事件监听的简写,等价于 v-on:click
  • ref() 是 Vue 3 提供的响应式 API,用于创建可变的响应式数据。
  • setup 语法糖让代码更简洁,无需再写 export default
  • scoped 属性确保样式只作用于当前组件,不会影响其他组件。

如何创建一个 SFC 文件:从零开始的步骤

要创建一个 Vue3 单文件组件,你不需要复杂的工具链。只要有一个支持 .vue 文件的编辑器(如 VS Code)和正确的项目环境即可。

步骤一:初始化 Vue3 项目

使用 Vue CLI 或 Vite 快速创建项目。推荐使用 Vite,因为它启动更快,更适合现代开发。

npm create vite@latest my-vue-app -- --template vue

cd my-vue-app

npm install

npm run dev

✅ 提示:--template vue 表示使用 Vue 模板,会自动配置好 Vue3 的支持。

步骤二:创建第一个 SFC 组件

src/components 目录下新建一个文件,例如 HelloWorld.vue

<!-- src/components/HelloWorld.vue -->
<template>
  <!-- 模板:定义组件的 UI 结构 -->
  <div class="hello">
    <h2>{{ title }}</h2>
    <p>当前时间:{{ currentTime }}</p>
  </div>
</template>

<script setup>
// 导入必要的 Vue 功能
import { ref, onMounted, onUnmounted } from 'vue'

// 定义响应式数据
const title = ref('Hello, Vue3!')
const currentTime = ref('')

// 使用 onMounted 钩子在组件挂载后启动定时器
onMounted(() => {
  // 每秒更新一次时间
  const timer = setInterval(() => {
    currentTime.value = new Date().toLocaleTimeString()
  }, 1000)

  // 清理函数:组件卸载时停止定时器,防止内存泄漏
  onUnmounted(() => clearInterval(timer))
})
</script>

<style scoped>
.hello {
  padding: 20px;
  background-color: #e8f5e8;
  border: 1px solid #4caf50;
  border-radius: 8px;
  text-align: center;
  margin-bottom: 20px;
}

h2 {
  color: #2e7d32;
}

p {
  color: #555;
  font-size: 14px;
}
</style>

📌 关键点:

  • onMountedonUnmounted 是 Vue3 的生命周期钩子,分别在组件挂载和卸载时执行。
  • 使用 setInterval 模拟实时更新,但必须在 onUnmounted 中清除,否则会引发内存泄漏。
  • scoped 样式避免污染全局。

数据绑定与事件处理:让组件“动起来”

Vue3 的核心能力之一就是响应式数据绑定。当数据变化时,视图会自动更新,无需手动操作 DOM。

响应式数据绑定

<template>
  <div>
    <input v-model="inputText" placeholder="输入文字" />
    <p>你输入的内容是:{{ inputText }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'

// 声明一个响应式变量,用于绑定输入框
const inputText = ref('')

// 当 inputText 改变时,视图会自动更新
// 这就是 Vue 的双向绑定(v-model)的原理
</script>

v-modelv-bind:valuev-on:input 的语法糖,简化了表单绑定。

事件处理

<template>
  <button @click="handleClick">点击我</button>
  <button @click="handleClickWithParam('参数传入')">带参数点击</button>
</template>

<script setup>
import { ref } from 'vue'

// 定义事件处理函数
function handleClick() {
  alert('按钮被点击了!')
}

// 传参方式:使用箭头函数包装
function handleClickWithParam(param) {
  alert(`收到参数:${param}`)
}
</script>

💬 小贴士:Vue 事件处理支持传参,但要避免直接写 @click="func(param)",因为会立即执行。推荐使用箭头函数或方法绑定。

组件通信:父传子与子传父

组件不是孤立存在的,它们需要通信。Vue3 中通过 propsemit 实现父子组件通信。

父组件传递数据给子组件(props)

<!-- ParentComponent.vue -->
<template>
  <div>
    <h3>父组件</h3>
    <ChildComponent :title="parentTitle" :count="counter" @update-count="updateCounter" />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'

const parentTitle = ref('这是父组件传来的标题')
const counter = ref(0)

function updateCounter(newCount) {
  counter.value = newCount
}
</script>

子组件接收并响应数据(emit)

<!-- ChildComponent.vue -->
<template>
  <div class="child">
    <h4>{{ title }}</h4>
    <p>当前计数:{{ count }}</p>
    <button @click="increment">+1</button>
    <button @click="decrement">-1</button>
  </div>
</template>

<script setup>
import { defineEmits, defineProps } from 'vue'

// 声明接收的 props
const props = defineProps({
  title: String,
  count: Number
})

// 声明要触发的事件
const emit = defineEmits(['update-count'])

// 定义方法
function increment() {
  emit('update-count', props.count + 1)
}

function decrement() {
  emit('update-count', props.count - 1)
}
</script>

🧩 关键概念:

  • definePropsdefineEmits 是 Vue3 的新 API,用于显式声明输入输出。
  • 父组件通过 :title="xxx" 传递数据,子组件通过 props.title 获取。
  • 子组件通过 emit('update-count', value) 向父组件发送事件。

高级特性:使用 <script setup> 与组合式 API

<script setup> 是 Vue3 的重大革新,它让逻辑更集中、更可复用。它本质上是 setup() 函数的语法糖。

<!-- Counter.vue -->
<template>
  <div>
    <p>计数器:{{ count }}</p>
    <button @click="increment">+1</button>
    <button @click="decrement">-1</button>
    <button @click="reset">重置</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'

// 使用 ref 定义响应式状态
const count = ref(0)

// 定义方法
function increment() {
  count.value++
}

function decrement() {
  count.value--
}

function reset() {
  count.value = 0
}
</script>

✅ 优势:

  • 无需 return,所有变量和函数直接暴露给模板使用。
  • 更适合逻辑复用(配合自定义 Composables)。
  • 代码更简洁,可读性更强。

结语:掌握 SFC,开启高效开发之旅

Vue3 创建单文件组件(SFC) 是现代前端开发的基石。通过将模板、逻辑和样式统一在一个文件中,我们不仅提升了代码的可维护性,也大幅降低了协作成本。无论是简单的按钮组件,还是复杂的页面模块,SFC 都能让你的开发更高效、更清晰。

从今天开始,尝试为每一个功能模块创建一个独立的 .vue 文件,你会发现:组件化不是“高级技巧”,而是“基础素养”。当你能熟练地拆分、组合、复用组件时,你就真正掌握了 Vue3 的精髓。

记住,每一个复杂的网页,都是由无数个小小的 SFC 组合而成的。从今天起,让每一个组件都像一块精心打磨的积木,共同构建属于你的数字世界。