01|组件注册与解析
第二章:组件模型与通信纵览 / 01。先解决一个最基础的问题:模板里的
<ButtonCounter />为什么能找到对应的.vue文件?
开始前先确认
本页沿用第一章的 Vite、Vue 3.5、<script setup lang="ts"> 环境。你需要已经会用 ref 保存局部状态;本页暂不使用 props 和 emits。
一个标签怎样变成组件实例
把下面两个文件放进已有 Vite 项目的 src/,由原来的 main.ts 挂载 App.vue。
<script setup lang="ts">
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button type="button" @click="count++">
已点击 {{ count }} 次
</button>
</template><script setup lang="ts">
import ButtonCounter from './ButtonCounter.vue'
</script>
<template>
<h1>独立计数器</h1>
<ButtonCounter />
<ButtonCounter />
</template>这里发生了四步:
- Vite 把
ButtonCounter.vue编译成一个组件定义对象。 <script setup>的顶层导入自动暴露给同文件模板,导入变量名就是局部组件名。- 渲染到每个
<ButtonCounter />时,Vue 都创建一个新的组件实例。 - 两个实例共用组件定义,却各自拥有一份
count;点第一个不会改变第二个。
“注册”解决名称到组件定义的映射,“挂载实例”才创建状态。不要把两件事混成“导入后组件已经运行”。
局部、全局与自动导入不是同一层能力
局部导入:默认选择
局部导入把依赖写在使用点。重命名、跳转定义、按引用裁剪和测试替换都有明确入口。对页面组件和业务组件,优先采用这种方式。
全局注册:应用级名称表
只有 main.ts 里注册到应用实例的组件,才能在任意后代模板中不经导入直接使用:
import { createApp } from 'vue'
import App from './App.vue'
import BaseButton from './components/BaseButton.vue'
const app = createApp(App)
app.component('BaseButton', BaseButton)
app.mount('#app')全局注册适合几乎每页都会出现、没有业务上下文的基础组件。把整套业务组件都放进全局表,会隐藏来源,也会让未使用组件更难从构建结果中移除。
自动导入:构建工具的约定
Vue 核心不会扫描目录并自动注册组件。相关能力通常来自 Vite 插件或上层框架。使用它时至少要能回答:扫描哪些目录、重名时选谁、类型声明生成在哪里、构建与测试是否使用同一规则。没有这些约定时,本页示例仍以显式导入为准。
名称解析的三个边界
| 使用位置 | 推荐写法 | 原因 |
|---|---|---|
.vue 单文件组件 | <UserCard /> | PascalCase 能与原生 HTML 标签明显区分,IDE 也容易定位 |
| 直接写进浏览器 DOM 的模板 | <user-card></user-card> | 浏览器先按不区分大小写的 HTML 解析,且自闭合自定义标签不可靠 |
| 动态组件 | :is="UserCard" | 直接传组件对象,不依赖字符串名称再次查表 |
若局部导入名与组件自身的递归名称冲突,显式导入优先。递归自引用的细节放在 第 11 篇。
失败实验:故意让解析失败
把 App.vue 中的导入删除,但保留 <ButtonCounter />。开发环境会出现“无法解析组件”一类警告,页面不会得到预期实例。按下面顺序定位:
- 文件路径和文件名大小写是否一致;
- 模板名称是否与导入变量一致;
- 是否误以为自动导入插件会在测试或当前目录生效;
- 是否把原生自定义元素当作 Vue 组件,或反过来。
不要用“全部全局注册”掩盖解析错误;那只会把一个局部错误变成应用级隐式依赖。
变体:Options API 的局部注册
维护旧组件时会看到显式 components 表:
<script lang="ts">
import { defineComponent } from 'vue'
import ButtonCounter from './ButtonCounter.vue'
export default defineComponent({
components: { ButtonCounter }
})
</script>它与 <script setup> 顶层导入解决的是同一个映射问题,不代表组件实例共享状态。
练习:验证三种解析结果
在一个最小 Vite 项目中完成以下合同:
LocalCounter.vue内部保存自己的计数,App.vue通过局部导入渲染两个实例;点击其中一个三次,页面必须显示3和0。BaseBadge.vue只渲染静态文字,在main.ts以BaseBadge全局注册,App.vue不导入也能使用一次。- 临时把
<LocalCounter />改名为<MissingCounter />,记录控制台的解析失败;随后恢复正确名称。 - 不使用自动导入插件,不新增依赖。
完成证据是三张可复现的观察:两个计数互不影响、全局组件能解析、错误名称会产生明确警告。
参考答案:查看第 2 章第 01 题参考答案。
通关自检:你能分别指出“组件定义、注册映射、组件实例”在例子中的位置,并解释为什么两个计数器不会共享 count。