深入解析 Vue 包:vue 究竟导出了什么?

上周帮团队重构一个遗留 Vue 2 + Options API 的中后台项目,升级到 Vue 3.4 后突然发现 Vue.extend 报错、Vue.set 找不到——不是语法错了,是 vue 这个包根本没导出它们。我们花了 3 小时翻 node_modules/vue/dist/vue.runtime.esm-bundler.jstypes/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 包导出的是“按需加载的能力契约”,不是历史包袱的集合。

  1. 永远优先使用具名导入(import { ref } from 'vue'),禁用默认导入
  2. 响应式 API 只在 setup 或组合式函数中调用,否则报错
  3. 放弃 Vue 2 全局 API 思维,Vue.xxx 形式全部替换为具名导入
  4. script setup 中,用 defineProps 替代 props: {} 选项
  5. 检查 node_modules/vue/package.jsonexports 字段,确认你用的导出是否在官方支持列表中

延伸思考:如果某天你需要自定义一个类似 ref 的响应式原语,它的导出签名应该包含哪些必要字段?

彩蛋 / 额外推荐

推荐三个帮你“看见”导出的工具:vue-tsc --noEmit --declaration --emitDeclarationOnly 查看生成的 d.ts;unplugin-auto-import 自动导入常用 API;Vue 官方 playground 的 “Show compiled output” 功能——它会实时展示 script setup 如何被编译成标准 setup 函数,导出逻辑一目了然。


关注我

我的微信公众号:前端开发博客,在后台回复以下关键字可以获取资源。

  • 回复「小抄」,领取Vue、JavaScript 和 WebComponent 小抄 PDF
  • 回复「Vue脑图」获取 Vue 相关脑图
  • 回复「思维图」获取 JavaScript 相关思维图
  • 回复「简历」获取简历制作建议
  • 回复「简历模板」获取精选的简历模板
  • 回复「加群」进入500人前端精英群
  • 回复「电子书」下载我整理的大量前端资源,含面试、Vue实战项目、CSS和JavaScript电子书等。
  • 回复「知识点」下载高清JavaScript知识点图谱

每日分享有用的前端开发知识,加我微信:caibaojian89 交流