Skip to content

01|组件注册与解析 ​

第二章:组件模型与通信纵览 / 01。先解决一个最基础的问题:模板里的 <ButtonCounter /> 为什么能找到对应的 .vue 文件?

开始前先确认 ​

本页沿用第一章的 Vite、Vue 3.5、<script setup lang="ts"> 环境。你需要已经会用 ref 保存局部状态;本页暂不使用 props 和 emits。

一个标签怎样变成组件实例 ​

把下面两个文件放进已有 Vite 项目的 src/,由原来的 main.ts 挂载 App.vue。

vue
<script setup lang="ts">
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
  <button type="button" @click="count++">
    已点击 {{ count }} 次
  </button>
</template>
vue
<script setup lang="ts">
import ButtonCounter from './ButtonCounter.vue'
</script>

<template>
  <h1>独立计数器</h1>
  <ButtonCounter />
  <ButtonCounter />
</template>

这里发生了四步:

  1. Vite 把 ButtonCounter.vue 编译成一个组件定义对象。
  2. <script setup> 的顶层导入自动暴露给同文件模板,导入变量名就是局部组件名。
  3. 渲染到每个 <ButtonCounter /> 时,Vue 都创建一个新的组件实例。
  4. 两个实例共用组件定义,却各自拥有一份 count;点第一个不会改变第二个。

“注册”解决名称到组件定义的映射,“挂载实例”才创建状态。不要把两件事混成“导入后组件已经运行”。

局部、全局与自动导入不是同一层能力 ​

局部导入:默认选择 ​

局部导入把依赖写在使用点。重命名、跳转定义、按引用裁剪和测试替换都有明确入口。对页面组件和业务组件,优先采用这种方式。

全局注册:应用级名称表 ​

只有 main.ts 里注册到应用实例的组件,才能在任意后代模板中不经导入直接使用:

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 />。开发环境会出现“无法解析组件”一类警告,页面不会得到预期实例。按下面顺序定位:

  1. 文件路径和文件名大小写是否一致;
  2. 模板名称是否与导入变量一致;
  3. 是否误以为自动导入插件会在测试或当前目录生效;
  4. 是否把原生自定义元素当作 Vue 组件,或反过来。

不要用“全部全局注册”掩盖解析错误;那只会把一个局部错误变成应用级隐式依赖。

变体:Options API 的局部注册 ​

维护旧组件时会看到显式 components 表:

vue
<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。

用心记录每一步成长