一、问题现象
项目环境:
| 环境 | 版本 |
|---|---|
| Vue | 3.5.26 |
| Vite | 6.4.1 |
| TypeScript | 5.6.3 |
| vue-tsc | 2.1.10 |
| unplugin-vue-components | 最新版本 |
项目已经配置了 unplugin-vue-components,启动后:
- ✅
components.d.ts正常生成 - ✅ 项目可以正常运行
- ✅ 全局组件可以直接使用
- ❌ 鼠标悬浮组件显示
(property) Pagination: any - ❌ Ctrl + 左键无法跳转到组件
- ❌ Trae / VSCode 没有组件类型提示
例如:
<template>
<Pagination />
</template>
鼠标悬浮提示:
(property) Pagination: any
而不是:
const Pagination: DefineComponent<...>
这说明自动导入成功了,但 TypeScript 类型没有生效。
二、第一步:怀疑 unplugin-vue-components 配置
首先检查插件配置:
export default function createAutoImportComponents() {
return Components({
dirs: ["src/components"],
resolvers: [ElementPlusResolver()],
dts: "src/types/components.d.ts",
});
}
没有问题。
生成的 components.d.ts 也正常:
declare module "vue" {
export interface GlobalComponents {
Pagination:
typeof import("./../components/Pagination/index.vue")["default"];
}
}
说明:
插件已经完成了它的工作。
于是继续排查。
三、检查 tsconfig
检查:
{
"include": [
"src",
"src/**/*.ts",
"src/**/*.d.ts",
"src/types/components.d.ts"
]
}
没有问题。
四、检查 env.d.ts
检查:
declare module "*.vue" {
import type { DefineComponent } from "vue"
const component: DefineComponent<{}, {}, any>
export default component
}
没有问题。
五、开始怀疑编辑器
由于使用的是 Trae,又怀疑:
- Volar 没生效?
- 插件版本有问题?
- Trae 对 Vue 支持不好?
后来验证:
- Vue 插件已安装
- Trae 打开其它 Vue3 项目完全正常
所以:
问题不是编辑器。
六、真正的突破口:vue-tsc
这一步非常关键。
执行:
npx vue-tsc --noEmit
结果直接报错:
Module '"vue"' has no exported member 'App'
看到这里,我意识到:
问题已经不是自动导入。
而是:
整个 Vue 类型系统已经异常。
如果连:
import type { App } from "vue"
都失败了,
那么:
- DefineComponent
- GlobalComponents
- Hover
- Ctrl+点击
全部都会受到影响。
七、继续定位 Vue 类型为什么失效
开始检查:
npm ls vue
正常:
vue@3.5.26
继续:
npm ls @vue/runtime-core
正常:
@vue/runtime-core@3.5.26
继续:
npm ls @types/vue
结果:
(empty)
说明:
- Vue 没装错
- 没有多个 Vue
- 没有 @types/vue 冲突
依赖完全正常。
八、真正的问题终于出现了
检查项目里的:
src/types/global.d.ts
发现里面有:
declare module "vue" {
interface ComponentInternalInstance {
proxy: any;
}
}
而整个文件没有:
export {}
也就是说:
整个 global.d.ts 是一个 Script。
而不是:
Module。
九、为什么这会导致类型全部失效?
很多人不知道:
TypeScript 的:
declare module "vue"
有两种完全不同的行为。
第一种:模块增强(正确)
export {}
declare module "@vue/runtime-core" {
interface ComponentInternalInstance {
proxy:any
}
}
这叫:
Module Augmentation
意思是:
我增强已有的 Vue 类型。
官方类型仍然存在。
第二种:错误声明
如果没有:
export {}
整个文件就是 Script。
这时候:
declare module "vue"
很可能被 TypeScript 当成:
我重新声明了一个 vue 模块。
于是:
官方所有导出:
- App
- DefineComponent
- Component
- Ref
全部消失。
于是出现:
Module '"vue"' has no exported member 'App'
而这正是我遇到的问题。
十、最终解决方案
第一步:
改成模块文件:
export {}
第二步:
不要增强:
declare module "vue"
而是:
declare module "@vue/runtime-core" {
interface ComponentInternalInstance {
proxy:any
}
}
第三步:
删除重复的:
declare module "*.vue"
项目里保留一份即可。
十一、修改后的结果
再次执行:
npx vue-tsc --noEmit
无报错。
然后:
- Hover 正常
- Ctrl+点击恢复
- 组件类型恢复
- 自动补全恢复
- Trae 类型提示恢复
整个项目恢复正常。
十二、整个排查过程总结
很多人遇到这种问题时,第一反应都是:
components.d.ts
↓
tsconfig
↓
Volar
↓
编辑器
↓
重装 node_modules
其实真正的排查顺序应该是:
自动导入失效?
│
▼
components.d.ts 是否生成?
│
├────没有
│
└──检查 Components 配置
│
▼
components.d.ts 已生成?
│
▼
运行
npx vue-tsc --noEmit
│
├────报错
│
└──先修复 Vue 类型系统
│
▼
vue-tsc 正常
│
▼
再检查
Hover
Ctrl+Click
Volar
十三、为什么自动导入正常,却没有类型?
这里很多人容易混淆。
实际上:
unplugin-vue-components
只负责:
扫描组件
↓
生成 components.d.ts
↓
编译时自动补 import
它并不负责:
- Hover
- Ctrl+点击
- 类型推导
这些全部依赖:
TypeScript 类型系统。
所以:
自动导入插件可以正常工作,但只要 Vue 类型系统异常,编辑器依然只能推导出
any。
十四、我的几点建议
经过这次排查,我总结了几条经验:
① 不要一看到 Hover 为 any,就怀疑自动导入插件。
很多时候插件其实已经正常工作。
② 第一时间执行:
npx vue-tsc --noEmit
这是定位 Vue 类型问题最快的方法。
③ 尽量不要在 global.d.ts 中直接增强 vue 模块。
推荐:
export {}
declare module "@vue/runtime-core" {
...
}
而不是:
declare module "vue"
④ *.vue 模块声明只保留一份即可。
避免:
env.d.ts
global.d.ts
重复声明。
总结
这次问题最大的收获,不是修复了自动导入,而是理清了 Vue 类型系统、TypeScript、Volar 与自动导入插件之间的关系。
一句话总结整个问题:
如果
components.d.ts已正常生成,但 Hover 显示any、Ctrl+点击失效,不要继续折腾unplugin-vue-components。先执行vue-tsc --noEmit,确认 Vue 类型系统是否正常。很多时候,真正的问题并不在自动导入,而在 TypeScript 类型环境。
后记
这篇文章是在一次真实的项目排查过程中整理出来的。从最初怀疑 unplugin-vue-components,到检查 tsconfig、env.d.ts、编辑器插件,再到最终定位到 global.d.ts 中的模块增强问题,整个过程最大的体会就是:
排查问题时,与其不断尝试修改配置,不如找到一个能够反映系统真实状态的切入点。
对于 Vue 项目来说,npx vue-tsc --noEmit 就是这样一个切入点。它往往能比编辑器提示更早、更准确地暴露类型系统中的根本问题。希望这篇踩坑记录,也能帮你少走一些弯路。