1.全局组件
全局组件是在 main.ts 中一次性注册,之后就可以在项目中的任何 组件模板内直接使用,无需 import。
组件本身就是一个标准的 SFC (单文件组件),使用 <script setup lang="ts"> 编写。
举个栗子,button组件
ts
<template>
<button class="base-button">
<slot></slot>
</button>
</template>
<script setup lang="ts">
// 这里可以定义 props, emits 等
// 例如:
// defineProps<{ type: 'primary' | 'secondary' }>()
</script>
<style scoped>
.base-button {
padding: 8px 16px;
border: 1px solid #ccc;
border-radius: 4px;
cursor: pointer;
}
.base-button:hover {
background-color: #f0f0f0;
}
</style>
在创建 Vue 实例后、挂载 (.mount()) 之前来注册它。
app.component('组件名', 组件对象)
ts
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
// 1. 导入要全局注册的组件
import BaseCard from './components/BaseCard.vue'
import BaseButton from './components/BaseButton.vue'
import SvgIcon from './components/SvgIcon.vue'
const app = createApp(App)
// 2. 使用 app.component() 进行全局注册
// app.component('组件名', 组件对象)
app.component('BaseCard', BaseCard)
app.component('BaseButton', BaseButton)
app.component('SvgIcon', SvgIcon)
// 3. 挂载应用
app.mount('#app')
注册后,在任何 其他组件中都可以直接使用 <BaseButton>,无需导入。
ts
<template>
<div>
<h1>欢迎!</h1>
<BaseButton>点我</BaseButton>
<BaseCard>
<SvgIcon name="user" />
<p>一些内容</p>
</BaseCard>
</div>
</template>
<script setup lang="ts">
// 无需 import BaseButton, BaseCard, SvgIcon
</script>
使用场景
- 基础 UI 组件: 这是最常见的场景。项目中使用频率极高的组件,例如
Button,Icon,Modal,Card,Input等。通常会以Base-或App-作为前缀,以示区分。 - UI 库集成: 当使用像 Element Plus, Naive UI 或 Vuetify 这样的库时,它们通常会提供一个
app.use(Library)的方式,这背后其实就是全局注册了它们所有的组件。 - 布局组件:
AppHeader,AppFooter,Sidebar等几乎每个页面都会用到的布局框架。
⚠️ 注意: 全局注册会轻微增加应用的初始加载体积 ,因为所有全局组件都会被打包到主
chunk中。因此,请对那些真正常用的组件使用全局注册,避免滥用。
2.递归组件
递归组件是指在其模板中调用自身的组件。
举个栗子:树形菜单
首先,定义数据结构 ( types.ts)
ts
// types.ts
export interface TreeNodeData {
id: string;
label: string;
children?: TreeNodeData[]; // 关键:children 数组的类型是它自身
}
然后,创建递归组件 (TreeNode.vue)
ts
<template>
<div class="tree-node">
<div class="node-label">{{ node.label }}</div>
<!-- 可选链操作符?.:如果 node.children 存在且有值,则渲染子节点列表,否则返回undefined,隐式转换为false -->
<ul v-if="node.children && node.children.length > 0" class="children-list">
<li v-for="child in node.children" :key="child.id">
<!-- 递归 -->
<TreeNode :node="child" />
</li>
</ul>
</div>
</template>
<script setup lang="ts">
// 导入我们定义的类型
import type { TreeNodeData } from './types'
// 1. 定义 Props,接收父组件传递的数据
interface Props {
node: TreeNodeData;
}
defineProps<Props>()
</script>
<style scoped>
.tree-node {
margin-left: 20px;
}
.node-label {
font-weight: bold;
}
.children-list {
list-style-type: none;
padding-left: 15px;
border-left: 1px dashed #ccc;
}
</style>
在父组件中导入并渲染"根节点"即可。
ts
<template>
<div>
<h1>文件结构树</h1>
<TreeNode :node="fileTree" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TreeNode from './components/TreeNode.vue'
import type { TreeNodeData } from './components/types' // 确保路径正确
// 准备一个符合 TreeNodeData 结构的 TS 数据
const fileTree = ref<TreeNodeData>({
id: 'root',
label: '项目根目录 (src)',
children: [
{
id: 'c1',
label: 'components',
children: [
{ id: 'c1-1', label: 'TreeNode.vue' },
{ id: 'c1-2', label: 'BaseButton.vue' },
],
},
{
id: 'c2',
label: 'views',
children: [
{ id: 'c2-1', label: 'HomePage.vue' },
],
},
{
id: 'c3',
label: 'App.vue',
// 这个节点没有 children,递归将在此处停止
},
],
})
</script>
使用场景
- 树形结构: 任何需要展示层级关系的数据。例如:文件浏览器、组织架构图、导航菜单(尤其是多级下拉菜单)。
- 嵌套评论: 社交媒体或论坛中的评论区,一条评论可以有"回复"(子评论),子评论又可以有回复。
- JSON 格式化器: 展示一个 JSON 对象,如果某个值是对象或数组,就递归地调用组件来展示其内部。
参考文章
小满zs 学习Vue3 第十五章(全局组件,局部组件,递归组件)xiaoman.blog.csdn.net/article/det...