上周帮团队重构一个遗留 Vue 2 + Options API 的中后台项目,升级到 Vue 3.4 后突然发现 Vue.extend 报错、Vue.set 找不到——不是语法错了,是 vue 这个包根本没导出它们。我们花了 3 小时翻 node_modules/vue/dist/vue.runtime.esm-bundler.js 和 types/vue.d.ts,才理清:Vue 3 的导出不是“Vue 构造函数的平移”,而是一套按使用场景重新组织的、有明确边界和生命周期约束的 API 集合。这篇文章就帮你一次性厘清:你每天 import 的那些东西,到底是谁、在哪、为什么能被导入?我们会从导出结构讲起,对比 Vue 2/3 差异,拆解 5 类核心导出(响应式、组件、渲染、工具、类型),并给出生产环境必须规避的 3 个典型误用。全文无废话,代码可直接粘贴进 play.vuejs.org 验证。
先说清楚:vue 不是“一个对象”,而是一份“能力菜单”
想象你在点外卖——vue 包不是给你一整只烤鸭(Vue 构造函数),而是把鸭肉、鸭架、鸭汤、葱酱分装成 5 个独立餐盒。你只拿需要的:想做状态管理?拿 ref 盒;想挂载应用?拿 createApp 盒;想写 JSX?顺手加个 h 盒。Vue 3 彻底抛弃了“new Vue()”这种全局构造函数模式,所有能力都通过具名导出(named export)提供,且每个导出都有明确职责和调用时机限制。比如 ref() 只能在 setup 或组合式函数里调用,defineComponent() 是编译器友好的类型包装器,不是运行时必需品。它不导出 Vue.prototype 上的方法(如 $nextTick),那些全被收编进 getCurrentInstance() 返回的实例上下文中。
五大导出类别与真实用法
1. 响应式核心:ref, reactive, computed
问题:为什么 ref 必须用 .value?因为它是“带壳的响应式容器”,就像保温杯——变量是水,.value 是拧开杯盖的动作。
import { ref, reactive } from 'vue'
// ✅ 正确:ref 包裹基础类型,reactive 包裹对象
const count = ref(0) // → { value: 0 }
const state = reactive({ name: 'Alice' }) // → Proxy { name: 'Alice' }
// ❌ 错误:不要对 reactive 对象再套 ref
// const wrong = ref(state) // 多余且降低性能
适用场景:复杂逻辑抽离到组合式函数;不适用:Options API 的 data 选项内(那里用普通对象即可)。踩坑:在 setup() 外调用 ref() 会报错“must be called at the top of a setup function”——这是 Vue 的响应式依赖追踪机制强制要求的。
2. 应用与组件:createApp, defineComponent, defineAsyncComponent
createApp() 是唯一入口工厂,返回一个应用实例(类似“餐厅经理”),负责挂载、插件注册;defineComponent() 本质是类型提示工具,对 JS 用户无运行时影响,但能帮 TS 推导 props 类型。
// ✅ 推荐:显式 defineComponent,获得完整类型支持
export default defineComponent({
props: {
msg: String
},
setup(props) {
return () => h('div', props.msg)
}
})
3. 渲染与 DOM:h, renderSlot, vModelText
h 是 createElement 的别名,JSX 编译目标;vModelText 是 v-model 指令的底层实现,极少需手动调用。
常见问题
Vue 2 的 Vue.set() / Vue.delete() 去哪了?
没了。Vue 3 的 reactive 基于 Proxy,天然支持动态属性增删。直接赋值即可:state.newKey = 'value'。这是真正的“删代码即优化”。
为什么没有 Vue.prototype.$nextTick?
它被重命名为 nextTick 并作为具名导出:import { nextTick } from 'vue'。更轻量,且可在任何地方调用(包括非 setup 函数)。
可以 import Vue from ‘vue’ 吗?
可以,但不推荐。默认导出是 createApp 工厂函数(兼容旧写法),但会丢失 tree-shaking 机会。坚持用 import { ref } from 'vue'。
defineProps / defineEmits 是从哪来的?
它们是 仅在 <script setup> 中可用的编译时宏,不属于 vue 包导出,由 Vue 编译器注入。运行时不存在。
Composition API 能在 Vue 2 里用吗?
可以,通过 @vue/composition-api 插件,但导出路径不同(import { ref } from '@vue/composition-api'),且不支持 script setup。
总结
vue 包导出的是“按需加载的能力契约”,不是历史包袱的集合。
- 永远优先使用具名导入(
import { ref } from 'vue'),禁用默认导入 - 响应式 API 只在 setup 或组合式函数中调用,否则报错
- 放弃 Vue 2 全局 API 思维,
Vue.xxx形式全部替换为具名导入 - 在
script setup中,用defineProps替代props: {}选项 - 检查
node_modules/vue/package.json的exports字段,确认你用的导出是否在官方支持列表中
延伸思考:如果某天你需要自定义一个类似 ref 的响应式原语,它的导出签名应该包含哪些必要字段?
彩蛋 / 额外推荐
推荐三个帮你“看见”导出的工具:vue-tsc --noEmit --declaration --emitDeclarationOnly 查看生成的 d.ts;unplugin-auto-import 自动导入常用 API;Vue 官方 playground 的 “Show compiled output” 功能——它会实时展示 script setup 如何被编译成标准 setup 函数,导出逻辑一目了然。
