Vue 组件基础

组件是 Vue 的核心组织单元。一个 Vue 应用就是一棵组件树:根组件 App.vue 下面挂很多子组件,子组件下面还可以再嵌套子组件。每个组件封装自己的模板、数据、样式,可独立复用。本章讲清组件的基本用法。

1. 为什么需要组件?

想象你把整个网站写在一个文件里——那会是几千行的"巨型代码"。组件化就是把页面拆成独立、可复用的小块:

2. 单文件组件(SFC)结构

Vue 的组件以 .vue 文件为单位,叫"单文件组件"(Single-File Component, SFC)。每个文件分三块:template、script、style。

<!-- Counter.vue —— 一个单文件组件 -->
<template>
  <div class="counter">
    <button @click="dec">-</button>
    <span>{{ count }}</span>
    <button @click="inc">+</button>
  </div>
</template>

<script setup>
import { ref } from "vue";
const count = ref(0);
const inc = () => count.value++;
const dec = () => count.value--;
</script>

<style scoped>
.counter {
  display: inline-flex;
  gap: 12px;
  align-items: center;
}
button {
  padding: 6px 14px;
  background: #42b883;
  color: white;
  border: none;
  border-radius: 4px;
}
</style>

三块的职责:

3. 局部注册(推荐)

<script setup> 里 import 一个组件,就能在模板里用——这是局部注册,也是 90% 场景的写法。

<template>
  <!-- 用 PascalCase 或 kebab-case 都行,推荐前者 -->
  <Counter />
  <Counter></Counter>
  <counter />            <!-- 也能用,但不推荐 -->

  <!-- 同一组件可复用多次,状态互相独立 -->
  <Counter />
  <Counter />
</template>

<script setup>
import Counter from "./Counter.vue";   // 局部注册:导入即可用
</script>

命名规范:

4. 全局注册

有些组件(如全局通用的按钮、图标)在到处都用,逐个 import 太烦。可以全局注册:

// main.js
import { createApp } from "vue";
import App from "./App.vue";
import BaseButton from "./components/BaseButton.vue";

const app = createApp(App);

// 全局注册:任何地方都能用,不需要 import
app.component("BaseButton", BaseButton);
app.component("BaseInput", () => import("./components/BaseInput.vue"));  // 异步

app.mount("#app");

// 全局注册的代价:打包变大、不可被 tree-shake
// 推荐只在"基础 UI 组件"(按钮/图标)用全局,其他一律局部

全局注册的代价:打包体积变大、不能被 tree-shake 优化。所以推荐只对少量基础组件用全局,其他一律局部注册。

5. 组件的复用

同一个组件可以用多次,每个实例都有独立的状态。看这个例子:

<template>
  <!-- 用 PascalCase 或 kebab-case 都行,推荐前者 -->
  <Counter />
  <Counter></Counter>
  <counter />            <!-- 也能用,但不推荐 -->

  <!-- 同一组件可复用多次,状态互相独立 -->
  <Counter />
  <Counter />
</template>

<script setup>
import Counter from "./Counter.vue";   // 局部注册:导入即可用
</script>

三个 <Counter /> 是三个独立实例,各自的 count 互不影响。这是组件化的核心特性——封装一次,实例化无限次。

6. 动态组件

有时需要根据数据动态切换显示哪个组件(比如标签页切换)。用 <component :is> 实现:

<template>
  <!-- 用 component :is 切换组件 -->
  <component :is="currentTab" />

  <!-- 也可以传字符串(注册过的组件名) -->
  <component :is="tabName" />

  <!-- 异步组件:Vite 自动代码分割 -->
  <component :is="asyncComp" />
</template>

<script setup>
import { ref, shallowRef } from "vue";
import Home from "./Home.vue";
import User from "./User.vue";
import Setting from "./Setting.vue";

const tabs = { Home, User, Setting };
const currentTab = shallowRef(Home);

// 异步组件(按需加载)
const asyncComp = shallowRef(() => import("./Heavy.vue"));

function switchTab(name) {
  currentTab.value = tabs[name];
}
</script>

<!-- Vue 还提供了 <KeepAlive> 缓存组件状态 -->
<template>
  <KeepAlive>
    <component :is="currentTab" />
  </KeepAlive>
</template>

几个要点:

7. 插槽(slot)基础

组件除了接收数据(props),还能接收模板内容——这就是插槽。父组件把一段 HTML 传给子组件,子组件决定它在哪渲染。

<!-- Card.vue:用 slot 接收父组件传入的内容 -->
<template>
  <div class="card">
    <h3>{{ title }}</h3>
    <!-- 默认插槽:父组件传进来的内容显示在这里 -->
    <slot></slot>
  </div>
</template>

<script setup>
defineProps(["title"]);
</script>

<!-- 父组件使用 -->
<template>
  <Card title="用户信息">
    <!-- 这段会出现在 <slot> 处 -->
    <p>姓名:小明</p>
    <p>年龄:20</p>
  </Card>

  <!-- 没传内容时,显示 slot 的默认值 -->
  <Card title="空卡片" />
</template>

插槽让组件复用性大幅提升:同一个 Card 组件能装任何内容。还有具名插槽(v-slot:header)和作用域插槽(子组件向父组件回传数据),属于进阶用法,需要时查文档即可。

8. 组件的组织

实际项目里,组件按层级组织:

这种分层让代码可维护性大大提升。新手先记住"基础组件 + 页面组件"两层就够用。

9. 递归组件

一个有趣的高级用法:组件可以递归调用自己。常用于树形结构(菜单、组织架构、评论楼中楼):

<!-- TreeNode.vue —— 递归组件:渲染树形结构 -->
<template>
  <div class="node">
    <span>{{ node.name }}</span>
    <!-- 递归调用自己,组件名即文件名 -->
    <TreeNode
      v-for="child in node.children"
      :key="child.id"
      :node="child"
    />
  </div>
</template>

<script setup>
defineProps(["node"]);
</script>

<!-- 用法 -->
<!--
<Tree :root="{
  name: '根',
  children: [{ name: '子1', children: [...] }]
}" />
-->

递归组件的组件名来自文件名(或 name 选项),所以模板里直接用 <TreeNode> 就能递归。注意要有终止条件(没有 children 时不再递归),否则会栈溢出。

10. 组件的命名规范

业界有两套常见约定(来自 Vue 官方风格指南):

小结

这一章你掌握了 Vue 组件的基本用法:SFC 三块结构、局部与全局注册、命名规范、动态组件、插槽。下一章我们看父子组件之间如何通信——props 接收数据、emit 触发事件。

← 上一篇 Vue 表单与 v-model

下一篇 Vue Props 与 Emit

✈️💬