Vue3 has no default export?别慌!看完这篇彻底搞懂怎么解决
刚接触Vue3的时候,很多人可能会踩这个坑:明明写Vue2的时候,最后加一句export default App/HelloWorld就能用,怎么换了Vue3直接报“Vue3 has no default export”,或者项目启动不了组件找不到?别急,这根本不是代码写错了那么简单,而是Vue3本身做了一个有意为之的架构调整,今天咱们就把这个问题拆成大家最关心的几个小部分,慢慢理清楚。
为什么Vue3突然没有默认导出了?
很多人遇到报错第一反应是“Vue官方怎么瞎改?”其实不然,官方这次调整是为了Vue3更好用,背后有两个很扎实的理由:
拥抱ES Modules的最佳实践
ES Modules是JavaScript官方推出的模块化规范,现在已经成了前端开发的绝对主流,ES规范里其实有两种导出方式:命名导出(Named Exports)和默认导出(Default Exports),但官方文档和很多资深开发者都更推荐优先使用命名导出——因为命名导出更清晰,一眼就能看出这个模块提供了什么功能,而且不会有像默认导出那样“随便起个名字就能用但后续维护容易混淆”的问题。
Vue3的代码base是完全用TypeScript重构的,TypeScript的生态也更倾向于命名导出:比如类型提示会更精准,打包工具(像Rollup、Vite这些和Vue3绑定很深的工具)的Tree Shaking效果会更好,Tree Shaking是什么?简单说就是打包的时候把没用的代码“摇掉”,让最终的包体积更小,如果用默认导出,打包工具很难确定你会不会用到默认导出对象里的其他属性,就不敢轻易删;但用命名导出,你明确import了什么,它就只打包什么。
适配Vue3的多种构建产物
Vue3官方提供了好几种不同的构建产物,分别对应不同的使用场景:比如CDN引入用的UMD(支持浏览器全局变量、CommonJS、AMD),模块打包工具用的ESM(分开发版和生产版),还有服务端渲染用的CommonJS版。
如果只有默认导出,不同构建产物的适配会很麻烦——比如CDN引入的时候,我们习惯用Vue这个全局变量;但用ESM的时候,如果默认导出Vue对象,那又和命名导出的API冲突了,官方改成全命名导出后,不管你用哪种方式引入,API都是统一的,比如在CDN里可以写const { createApp, ref, computed } = Vue,在ESM里可以写import { createApp, ref, computed } from 'vue',完全一致,不用记两种写法。
常见的“Vue3 has no default export”报错场景有哪些?怎么解决?
知道了原因,接下来就是解决问题的核心环节,咱们梳理一下大家最常踩的几个场景:
场景1:导入Vue对象的时候用了默认导入
这是最简单也是最容易犯的错误,很多人刚从Vue2转过来,还习惯写import Vue from 'vue',然后用Vue.createApp(...),这时候浏览器或者终端肯定会报错“Vue3 has no default export”。
解决方法很简单:改成命名导入就行,正确的写法应该是import { createApp } from 'vue',然后直接用createApp(...)。
场景2:引入Vue3 UI组件库的时候用了错误的方式
这个场景稍微隐蔽一点,比如你用的是Element Plus或者Ant Design Vue这种基于Vue3的组件库,有些老教程可能还沿用Vue2的写法:先import Vue,再Vue.use(ElementPlus),这时候也会因为找不到默认的Vue对象报错。
解决方法:同样用命名导入,而且组件库的引入方式也变了,比如Element Plus,现在推荐在main.js/main.ts里这样写:
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')
哦?这里Element Plus怎么还能用默认导入?别慌,组件库的默认导出和Vue3核心包的全命名导出是两回事——很多UI组件库为了兼容旧习惯,还是保留了默认导出(其实就是把所有组件和插件包成一个对象),但你也可以用按需命名导入来减小包体积,
import { createApp } from 'vue'
import { ElButton, ElInput } from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
const app = createApp(App)
app.use(ElButton)
app.use(ElInput)
app.mount('#app')
场景3:用旧版的Vue CLI或者配置错误的打包工具创建项目
如果你用的是非常老的Vue CLI(比如3.x甚至更早)创建的Vue3项目,或者手动配置Webpack/Vite的时候没设置好ES Modules的解析规则,也可能会报错——不过这个场景现在很少见了,因为官方现在已经把Vite作为Vue3的默认构建工具,Vue CLI也在4.x之后支持了正确的Vue3配置。
解决方法:先升级构建工具到最新版,如果是手动配置的,记得检查以下几点:
- Vite的话,默认就是解析ES Modules的,不用改什么,但要确保package.json里有
"type": "module"这个字段(虽然Vite有时候能自动处理,但加上更保险)。 - Webpack的话,要确保用的是Webpack 5或者支持ES Modules的4.x版本,并且在webpack.config.js里设置
output: { module: true }或者target: 'web'(如果是浏览器环境),同时确保babel配置正确转译了ES Modules。
场景4:自定义组件或者工具库的导出/导入方式不匹配
这个场景和Vue3核心包没关系,但也是很多新手会遇到的“关联报错”——比如你写了一个自定义组件,在Vue3里官方推荐的写法是<script setup>或者用命名导出的<script>,但有些新手可能习惯了Vue2的默认导出,写了<script setup>之后又加了一句export default {},或者导入的时候用了默认导入却找不到组件。
这里要分两种情况说:
情况A:用了<script setup>语法糖
如果你用的是<script setup>,那根本不用手动写export——Vite或者Vue Loader会自动把这个文件里的顶层变量、函数、组件作为命名导出,同时也会自动生成一个默认导出的组件对象,专门给父组件用默认导入的方式引入用,哦?这里是不是有点矛盾?其实不是——自动生成的默认导出和Vue3核心包的全命名导出无关,是<script setup>的特性,为了兼容旧习惯。
比如你写了一个HelloWorld.vue:
<script setup>
import { ref } from 'vue'
const msg = ref('Hello Vue3!')
</script>
<template>
<div>{{ msg }}</div>
</template>
这个文件会自动被处理成:
- 有一个命名导出
HelloWorld(其实是自动生成的组件名,和文件名一致); - 有一个默认导出的组件对象,和命名导出的是同一个。
所以父组件不管用import HelloWorld from './HelloWorld.vue'还是import { HelloWorld } from './HelloWorld.vue'都可以正常使用——不过推荐用和文件名一致的默认导入或者命名导入,保持清晰。
情况B:没用<script setup>,用的是传统的<script>
如果你没用语法糖,那就要手动写export,但这里的选择比较多:
- 如果你想兼容旧习惯,可以用默认导出:
export default { name: 'HelloWorld', setup() { ... } };
- 如果你想拥抱命名导出,也可以这么写:
import { defineComponent, ref } from 'vue'
const HelloWorld = defineComponent({
name: 'HelloWorld',
setup() {
const msg = ref('Hello Vue3!')
return { msg }
}
})
export { HelloWorld }
这时候父组件就必须用命名导入了:import { HelloWorld } from './HelloWorld.vue'。
除了报错,全命名导出还有什么好处?
刚才我们提到了Tree Shaking和API统一,其实全命名导出还有很多“隐性好处”:
更容易做按需导入,优化包体积
比如你在写一个小项目,只用到了Vue3的ref、computed、onMounted这几个API,那你就可以直接命名导入这三个,其他没用的像reactive、watchEffect、provide/inject(如果不用的话)都会被打包工具摇掉,最终的包体积会比用默认导入小很多——尤其是生产环境,这一点很重要,包体积小意味着页面加载更快,用户体验更好。
类型提示更精准,开发效率更高
如果你用TypeScript写Vue3,那全命名导出的优势就更明显了:你命名导入什么,TypeScript就只会提示什么,不会有一堆无关的API干扰你的视线;而且如果你打错了API的名字,比如把ref写成了Ref(注意大小写,Vue3的API都是小写驼峰),TypeScript会立刻报错,帮你减少低级错误。
代码更清晰,维护成本更低
用命名导入的时候,你一眼就能看出这个文件用了Vue3的哪些功能,或者导入了哪些自定义组件;而用默认导入的时候,如果别人把默认导出的对象改了名字(虽然在Vue2里大家一般都叫App或者HelloWorld,但也有人随便起),或者默认导出对象里加了很多乱七八糟的属性,后续维护的时候就会很头疼。
总结一下
Vue3没有默认导出不是bug,而是官方为了拥抱ES Modules最佳实践、适配多种构建产物、优化Tree Shaking和类型提示做的有意调整,遇到“Vue3 has no default export”报错的时候,别慌,先检查一下:
- 是不是导入Vue3核心包的时候用了默认导入?是的话改成命名导入;
- 是不是引入UI组件库的时候用了Vue2的写法?是的话改成用createApp创建实例后再app.use;
- 是不是用了旧版的构建工具或者配置错误?是的话升级或者检查配置;
- 是不是自定义组件的导出/导入方式不匹配?是的话根据是否用了
<script setup>选择正确的方式。
建议大家尽快习惯用命名导入——这不仅是Vue3的趋势,也是整个前端开发的趋势,用久了你会发现,真的比默认导出好用很多。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
如果你没用语法糖,那就要手动写export,但这里的选择比较多:
- 如果你想兼容旧习惯,可以用默认导出:
export default { name: 'HelloWorld', setup() { ... } }; - 如果你想拥抱命名导出,也可以这么写:
import { defineComponent, ref } from 'vue' const HelloWorld = defineComponent({ name: 'HelloWorld', setup() { const msg = ref('Hello Vue3!') return { msg } } }) export { HelloWorld }这时候父组件就必须用命名导入了:
import { HelloWorld } from './HelloWorld.vue'。
除了报错,全命名导出还有什么好处?
刚才我们提到了Tree Shaking和API统一,其实全命名导出还有很多“隐性好处”:
更容易做按需导入,优化包体积
比如你在写一个小项目,只用到了Vue3的ref、computed、onMounted这几个API,那你就可以直接命名导入这三个,其他没用的像reactive、watchEffect、provide/inject(如果不用的话)都会被打包工具摇掉,最终的包体积会比用默认导入小很多——尤其是生产环境,这一点很重要,包体积小意味着页面加载更快,用户体验更好。
类型提示更精准,开发效率更高
如果你用TypeScript写Vue3,那全命名导出的优势就更明显了:你命名导入什么,TypeScript就只会提示什么,不会有一堆无关的API干扰你的视线;而且如果你打错了API的名字,比如把ref写成了Ref(注意大小写,Vue3的API都是小写驼峰),TypeScript会立刻报错,帮你减少低级错误。
代码更清晰,维护成本更低
用命名导入的时候,你一眼就能看出这个文件用了Vue3的哪些功能,或者导入了哪些自定义组件;而用默认导入的时候,如果别人把默认导出的对象改了名字(虽然在Vue2里大家一般都叫App或者HelloWorld,但也有人随便起),或者默认导出对象里加了很多乱七八糟的属性,后续维护的时候就会很头疼。
总结一下
Vue3没有默认导出不是bug,而是官方为了拥抱ES Modules最佳实践、适配多种构建产物、优化Tree Shaking和类型提示做的有意调整,遇到“Vue3 has no default export”报错的时候,别慌,先检查一下:
- 是不是导入Vue3核心包的时候用了默认导入?是的话改成命名导入;
- 是不是引入UI组件库的时候用了Vue2的写法?是的话改成用createApp创建实例后再app.use;
- 是不是用了旧版的构建工具或者配置错误?是的话升级或者检查配置;
- 是不是自定义组件的导出/导入方式不匹配?是的话根据是否用了
<script setup>选择正确的方式。
建议大家尽快习惯用命名导入——这不仅是Vue3的趋势,也是整个前端开发的趋势,用久了你会发现,真的比默认导出好用很多。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网



