
三种组件用法
Vue 3 里常见三种用法。全局组件注册一次,整个应用都能用;局部组件只在导入它的文件里能用;递归组件在自己的模板里调用自己,用来渲染树形结构。
| 用法 | 注册方式 | 适用场景 |
|---|---|---|
| 全局组件 | app.component(name, component) | Button、Icon 这类到处都在用的基础组件 |
| 局部组件 | import 后直接在模板里用 | 绝大多数业务组件 |
| 递归组件 | 组件内引用自身 | 树、菜单、级联选择器 |
全局组件
全局注册的组件在整个应用的任何模板里都能直接使用,不用再次导入。注册方法是 app.component(name, component)。
注册与使用
先写一个 Card 组件:
<template> <div class="card"> <div class="card-header"> <div>标题</div> <div>副标题</div> </div> <div v-if="content" class="card-content"> {{ content }} </div> </div></template>
<script setup lang="ts">type Props = { content: string}
defineProps<Props>()</script>在 main.ts 里注册,然后挂载应用:
import { createApp } from 'vue'import App from './App.vue'import Card from './components/Card/index.vue'
const app = createApp(App)app.component('Card', Card) // 全局注册app.mount('#app')注册之后,任何组件的模板里都能直接写 <Card />:
<template> <Card content="我是全局卡片内容" /></template>注册顺序
顺序是 createApp → component → mount,注册要写在 app.mount() 之前。
批量注册
图标这类高频组件可以遍历注册。下面以 Element Plus 的图标为例:
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
const app = createApp(App)for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component)}全局注册的代价
全局注册的组件不参与 Tree-shaking。即使某个页面没有用到,它依然会出现在打包结果里。
依赖关系也不清晰:看到模板里的 <Card />,你无法从当前文件判断它的实现来自哪里。组件多起来以后,这类定位很费时间。
局部组件
局部注册的组件只能在使用它的父组件里使用。在 <script setup> 中,import 之后就能直接在模板里写,不用再注册。
<template> <div class="wraps"> <LayoutHeader /> <LayoutMenu /> <LayoutMain /> </div></template>
<script setup lang="ts">import LayoutHeader from './Header.vue'import LayoutMenu from './Menu.vue'import LayoutMain from './Content.vue'</script>作用域是隔离的:A 组件导入了 B,只有 A 能用 B。C 想用 B,得自己再 import 一次。
局部注册的依赖关系清晰,没被用到的组件不会进入打包结果,所以它是默认选择。
递归组件
递归能停下来,靠模板里的终止条件:没有子节点时不再渲染下一层。组件名不是必须的,单文件组件可以直接用文件名引用自己。
数据与类型
树形数据用递归类型描述,children 指向同一种类型:
type TreeList = { name: string children?: TreeList[]}
const data = reactive<TreeList[]>([ { name: 'no.1', children: [{ name: 'no.1-1', children: [{ name: 'no.1-1-1' }] }] }, { name: 'no.2', children: [{ name: 'no.2-1' }] }, { name: 'no.3' }])父组件把这份数据交给递归组件:
<template> <TreeItem :data="data" /></template>组件名称
单文件组件可以直接用文件名引用自己,不必声明 name。从 3.2.34 起,<script setup> 会按文件名推导组件名,TreeItem.vue 推导出来的名字就是 TreeItem。
需要显式声明 name 的场景是 <KeepAlive> 的 include 和 exclude:它们按组件名匹配要缓存的组件,没有推导出名字时就得自己写。Vue 3.3 起用 defineOptions 设置:
<script setup lang="ts">defineOptions({ name: 'TreeItem' })</script>3.3 之前的项目用两个 <script> 标签,一个只用来导出 name,另一个写 <script setup>:
<script lang="ts">export default { name: 'TreeItem'}</script>
<script setup lang="ts">interface Tree { name: string children?: Tree[]}
defineProps<{ data?: Tree[] }>()</script>用宏插件设置 name 的做法,3.3 起有了 defineOptions,不再必要。
递归模板与终止条件
<template> <div class="tree" style="margin-left: 10px"> <div v-for="(item, index) in data" :key="index"> <div @click="clickItem(item)">{{ item.name }}</div>
<TreeItem v-if="item?.children?.length" :data="item.children" /> </div> </div></template>v-if="item?.children?.length" 就是终止条件。没有子节点时不渲染下一层,递归到此结束。
Vue 3 学习笔记
连载中分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 208 天
部分内容可能已过时