Vue3 单文件中不同的组件
在 Vue 3 的单文件组件(.vue 文件)中,组件可以从多个层面来理解。下面我将详细说明哪些部分算是组件,以及如何识别它们。
📁 组件定义的多个层次
1. 文件本身就是一个组件
这是最直观的理解:每个 .vue 文件本身就是一个 Vue 组件。
vue
<!-- UserCard.vue - 这个文件本身就是一个组件 -->
<template>
<div class="user-card">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const user = ref({
name: '张三',
email: 'zhangsan@example.com'
})
</script>
识别要点:
- 文件扩展名是
.vue - 包含标准的 Vue 组件结构(template/script/style)
- 可以被其他组件导入和使用
2. 组件中的子组件
在单个 .vue 文件中,可以在 <template> 中使用的自定义标签都是组件。
示例1:使用全局注册的组件
vue
<!-- ParentComponent.vue -->
<template>
<div>
<!-- 这些都是组件 -->
<el-button>按钮组件</el-button> <!-- Element Plus 组件 -->
<el-input placeholder="输入框组件" /> <!-- Element Plus 组件 -->
<router-view /> <!-- Vue Router 组件 -->
</div>
</template>
示例2:使用局部注册的组件
vue
<!-- ParentComponent.vue -->
<template>
<div>
<!-- 这些都是子组件 -->
<ChildComponentA />
<ChildComponentB />
<UserAvatar :user="currentUser" />
</div>
</template>
<script setup>
// 导入的组件
import ChildComponentA from './ChildComponentA.vue'
import ChildComponentB from './ChildComponentB.vue'
import UserAvatar from './UserAvatar.vue'
// 在 <script setup> 中,导入的组件会自动在模板中可用
</script>
3. 动态组件
使用 Vue 的 <component> 标签配合 :is 属性也是组件。
vue
<template>
<div>
<!-- 动态组件 -->
<component :is="currentComponent" />
<!-- 也可以是异步组件 -->
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<LoadingSpinner />
</template>
</Suspense>
</div>
</template>
<script setup>
import { shallowRef, defineAsyncComponent } from 'vue'
import HomePage from './HomePage.vue'
import AboutPage from './AboutPage.vue'
// 动态组件
const currentComponent = shallowRef(HomePage)
// 异步组件
const AsyncComponent = defineAsyncComponent(() =>
import('./AsyncComponent.vue')
)
</script>
4. 内置组件
Vue 3 提供的内置组件,虽然不写在文件中,但使用它们也算作组件。
vue
<template>
<!-- 内置组件 -->
<Teleport to="body">
<Modal />
</Teleport>
<KeepAlive>
<component :is="currentComponent" />
</KeepAlive>
<Transition name="fade">
<div v-if="show">过渡内容</div>
</Transition>
</template>
5. 函数式组件
在同一个文件中定义的函数式组件。
vue
<!-- FunctionalComponentExample.vue -->
<template>
<div>
<!-- 使用函数式组件 -->
<DynamicHeading :level="1">标题1</DynamicHeading>
<DynamicHeading :level="2">标题2</DynamicHeading>
</div>
</template>
<script setup>
import { h } from 'vue'
// 函数式组件 - 这也是一个组件
const DynamicHeading = (props, { slots }) => {
return h(`h${props.level}`, {}, slots.default())
}
// 定义 props
DynamicHeading.props = ['level']
</script>
6. 渲染函数组件
在 <script> 中使用渲染函数定义的组件。
vue
<!-- RenderFunctionExample.vue -->
<script>
import { h, defineComponent } from 'vue'
// 渲染函数组件
const CustomButton = defineComponent({
props: {
type: {
type: String,
default: 'button'
}
},
setup(props, { slots }) {
return () => h(
'button',
{
class: 'custom-button',
type: props.type
},
slots.default()
)
}
})
export default defineComponent({
components: {
CustomButton
}
})
</script>
<template>
<CustomButton>点击我</CustomButton>
</template>
7. JSX 组件
如果在 Vue 3 中使用 JSX,那么在同一个文件中定义的 JSX 函数也是组件。
vue
<!-- JsxComponentExample.vue -->
<script>
import { defineComponent } from 'vue'
// JSX 组件
const JsxButton = defineComponent({
props: ['onClick'],
setup(props, { slots }) {
return () => (
<button onClick={props.onClick} class="jsx-button">
{slots.default()}
</button>
)
}
})
export default defineComponent({
components: {
JsxButton
}
})
</script>
🎯 实际项目示例分析
让我们通过一个完整的示例来识别其中所有的组件:
vue
<!-- App.vue -->
<template>
<div id="app">
<!-- 1. 路由组件(动态组件) -->
<router-view />
<!-- 2. UI 框架组件 -->
<el-button @click="showModal = true">打开弹窗</el-button>
<el-dialog v-model="showModal" title="提示">
<span>这是一个弹窗</span>
</el-dialog>
<!-- 3. 内置组件 -->
<Teleport to="body">
<!-- 4. 全局注册的组件 -->
<GlobalNotification />
</Teleport>
<!-- 5. 局部注册的组件 -->
<Header :title="appTitle" />
<Sidebar :menus="menuItems" />
<MainContent>
<!-- 6. 插槽中的组件 -->
<UserList :users="users" />
</MainContent>
<Footer />
<!-- 7. 条件渲染的组件 -->
<template v-if="user.isAdmin">
<AdminPanel />
</template>
<!-- 8. 循环渲染的组件 -->
<template v-for="item in featuredItems" :key="item.id">
<FeaturedCard :item="item" />
</template>
<!-- 9. 动态组件 -->
<component :is="currentTabComponent" />
</div>
</template>
<script setup>
import { ref, shallowRef, computed, defineAsyncComponent } from 'vue'
import { useRouter } from 'vue-router'
import { ElButton, ElDialog } from 'element-plus'
// 10. 导入的组件
import Header from './components/Header.vue'
import Sidebar from './components/Sidebar.vue'
import MainContent from './components/MainContent.vue'
import Footer from './components/Footer.vue'
import UserList from './components/UserList.vue'
// 11. 异步组件
const AdminPanel = defineAsyncComponent(() =>
import('./components/AdminPanel.vue')
)
// 12. 全局组件(已经在 main.js 中注册)
// GlobalNotification 已在全局注册
// 13. 函数式组件
const StatusBadge = (props, context) => {
// 渲染函数返回虚拟节点
return h('span', {
class: `badge badge-${props.type}`,
style: { backgroundColor: props.color }
}, context.slots.default())
}
StatusBadge.props = ['type', 'color']
// 14. 动态组件定义
const tabs = {
home: defineAsyncComponent(() => import('./views/Home.vue')),
about: defineAsyncComponent(() => import('./views/About.vue')),
contact: shallowRef({
template: '<div>联系我们</div>'
})
}
const currentTab = ref('home')
const currentTabComponent = computed(() => tabs[currentTab.value])
// 响应式数据
const showModal = ref(false)
const appTitle = ref('我的应用')
const menuItems = ref([/* ... */])
const users = ref([/* ... */])
const user = ref({ isAdmin: true })
const featuredItems = ref([/* ... */])
</script>
<style scoped>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
}
</style>
📊 组件分类总结
| 序号 | 组件类型 | 示例 | 定义位置 | 使用方式 |
|---|---|---|---|---|
| 1 | 文件组件 | App.vue 本身 |
独立的 .vue 文件 |
被其他文件导入 |
| 2 | 子组件 | Header, Sidebar |
其他 .vue 文件 |
在模板中作为标签使用 |
| 3 | UI 框架组件 | el-button, el-dialog |
Element Plus 库 | 在模板中作为标签使用 |
| 4 | 内置组件 | router-view, Teleport |
Vue 3 核心 | 在模板中作为标签使用 |
| 5 | 动态组件 | <component :is="..."> |
当前文件或导入 | 通过 :is 动态切换 |
| 6 | 异步组件 | defineAsyncComponent() |
通过函数定义 | 延迟加载 |
| 7 | 函数式组件 | StatusBadge 函数 |
在当前文件定义 | 通过函数调用或标签 |
| 8 | 渲染函数组件 | 使用 h() 函数 |
在 <script> 中 |
通过渲染函数 |
| 9 | 全局组件 | GlobalNotification |
在 main.js 注册 |
在任何地方使用 |
| 10 | 插槽组件 | <UserList> 在插槽内 |
子组件 | 通过插槽传递 |
🔍 如何识别组件
1. 在模板中识别
vue
<template>
<!-- 这些都是组件 -->
<ComponentName /> <!-- 自定义组件 -->
<el-button /> <!-- UI 框架组件 -->
<router-view /> <!-- 内置组件 -->
<component :is="comp" /> <!-- 动态组件 -->
</template>
识别规则:
- 以大写字母开头的标签(PascalCase)通常是组件
- 以短横线连接的标签(kebab-case)也可能是组件
- 使用自闭合标签
<ComponentName />通常是组件 - 使用非 HTML 标准标签的都是组件
2. 在 script 中识别
javascript
// 这些都是组件的定义或导入
import UserCard from './UserCard.vue' // 导入组件
const AsyncComp = defineAsyncComponent(...) // 定义异步组件
const FunctionalComp = () => h('div', ...) // 定义函数式组件
export default { components: { ... } } // 注册组件
3. 在实际代码中识别
vue
<template>
<!-- 组件示例 -->
<div>
<!-- 1. 原生 HTML 元素 -->
<div>这是 HTML 元素</div>
<button>这是 HTML 按钮</button>
<!-- 2. Vue 组件 -->
<MyComponent /> <!-- 自定义组件 -->
<el-button>按钮</el-button> <!-- UI 库组件 -->
<router-link to="/">首页</router-link> <!-- 路由组件 -->
<!-- 3. 内置组件 -->
<Transition>
<div v-if="show">内容</div>
</Transition>
<!-- 4. 动态组件 -->
<component :is="currentView" />
</div>
</template>
🎯 快速识别技巧
技巧1:查看导入语句
javascript
// 这些导入的都是组件
import Button from './Button.vue' // 文件组件
import { ElButton } from 'element-plus' // UI 库组件
import { RouterView } from 'vue-router' // 内置组件
技巧2:查看组件注册
javascript
// Options API
export default {
components: {
Button, // 局部注册的组件
ElButton, // UI 组件
RouterView // 内置组件
}
}
// Composition API (<script setup>)
// 导入的组件自动注册
技巧3:查看模板使用
vue
<template>
<!-- 组件特征 -->
<!-- 1. 属性绑定 -->
<Component :prop="value" @event="handler" />
<!-- 2. 插槽使用 -->
<Component>
<template #header>标题</template>
内容
</Component>
<!-- 3. 作用域插槽 -->
<Component v-slot="{ item }">
{{ item.name }}
</Component>
</template>
📈 实际项目中的应用
项目结构示例
src/
├── components/ # 可复用组件
│ ├── common/ # 通用组件
│ │ ├── Button.vue
│ │ ├── Input.vue
│ │ └── Modal.vue
│ └── layout/ # 布局组件
│ ├── Header.vue
│ ├── Sidebar.vue
│ └── Footer.vue
├── views/ # 页面组件
│ ├── Home.vue
│ ├── About.vue
│ └── User/
│ ├── List.vue
│ └── Detail.vue
└── App.vue # 根组件
组件使用示例
vue
<!-- Home.vue -->
<template>
<div class="home">
<!-- 布局组件 -->
<AppHeader />
<AppSidebar />
<!-- 页面内容 -->
<main class="content">
<!-- 功能组件 -->
<SearchBar v-model="searchText" />
<UserList :users="filteredUsers" />
<!-- UI 组件 -->
<el-pagination
:current-page="currentPage"
:total="total"
@current-change="handlePageChange"
/>
<!-- 内置组件 -->
<router-link to="/about">关于我们</router-link>
<!-- 动态组件 -->
<component :is="getComponentByType(user.type)" />
</main>
<!-- 布局组件 -->
<AppFooter />
</div>
</template>
🎯 总结
在 Vue 3 的单文件组件中,以下这些都算是组件:
- 文件本身 :每个
.vue文件就是一个组件 - 导入的子组件:从其他文件导入的 Vue 组件
- UI 框架组件:如 Element Plus、Ant Design Vue 等
- 内置组件 :Vue 3 提供的
<component>、<Transition>等 - 路由组件 :Vue Router 提供的
<router-view>、<router-link> - 动态组件 :通过
<component :is="...">动态渲染的组件 - 异步组件 :通过
defineAsyncComponent()定义的组件 - 函数式组件:通过函数定义的渲染函数组件
- 全局组件:在应用级别注册的组件
- 渲染函数组件 :在
<script>中使用h()函数定义的组件
核心要点:
- 组件是 Vue 应用的基本构建块
- 组件可以被复用、组合和嵌套
- 组件化是 Vue 的核心思想
- 理解组件的各种形式有助于更好地组织代码
简单的识别方法:
在 Vue 模板中,不是标准 HTML 标签 的元素,基本上都是组件。标准 HTML 标签包括:
div、span、p、a、button、input等约 100 个元素。其他自定义标签都是组件。
通过理解这些概念,您就能准确地识别 Vue 3 单文件中的各种组件,并正确地使用它们来构建应用。