配套代码见本仓库:
App.vue/components//composables/。
运行:npm install && npm run dev
一、Composition API 与 Options API 有什么区别?
Options API(Vue2 风格)
逻辑按选项类型组织:data、methods、computed、watch、生命周期 各写一块。
1 | export default { |
Composition API(Vue3 风格,<script setup>)
逻辑按功能组织:同一个功能的「状态 + 计算 + 方法 + 生命周期」写在一起。
1 | <script setup> |
核心区别
| 维度 | Options API | Composition API |
|---|---|---|
| 组织方式 | 按选项类型(data/methods…) | 按功能(相关业务逻辑聚合) |
| 逻辑复用 | mixins / 作用域插槽(易冲突、来源模糊) | composable 函数(显式返回、来源清晰) |
| TypeScript | this 类型推导困难 |
天然类型友好,无 this |
| 代码规模 | 组件越大,「跳读」越痛苦 | 相关逻辑聚拢,可维护性高 |
| 响应式入口 | data() 返回对象 |
ref / reactive 显式声明 |
this |
依赖 this 上下文 |
几乎不需要 this |
一句话:Options API 把代码「切碎」到不同选项里;Composition API 让你把「一个功能」的代码「聚拢」在一起,并能被抽成 composable 复用。
二、Vue3 的设计目标是什么?
- 更好的 TypeScript 支持:用 Class API /
this推导类型很痛苦,Vue3 用 Composition API + 类型友好的响应式原语,让 TS 成为一等公民。 - 更灵活的逻辑复用:取代 mixins 的缺陷(命名冲突、数据来源不清晰),用组合式函数(composable)做显式、可组合的复用。
- 更小的体积 & 更好的 Tree-shaking:基于 ES Module,未使用的特性不进包。
- 更好的性能:重写响应式系统(Proxy)、编译期优化,运行时更轻。
- 更易维护的大型应用:逻辑聚合、组合优于继承、暴露底层能力(
reactivity模块可单独用)。 - 渐进式 & 兼容:Options API 依旧保留,老项目可平滑过渡。
三、Vue3 做了哪些优化?
- 响应式系统重写:
Object.defineProperty→Proxy(见第五节)。 - 编译期优化:
- 静态节点提升(hoist):不变的节点只创建一次。
- 补丁标记(patch flag):diff 时只检查带 flag 的动态节点。
- 事件缓存、Block Tree 减少比对范围。
- Tree-shaking:见第六节。
- 更小的运行时:核心运行时精简,按需引入。
- Fragment / Teleport / Suspense 等新内置能力。
- 自定义渲染器 API:可脱离 DOM(如小程序、Canvas、Native)。
四、用 Vue3 如何设计一个 Modal 组件?
关键设计点(本项目 src/components/Modal.vue 已实现):
- Teleport 到 body:
<Teleport to="body">让弹窗脱离父级 DOM 层级,避免overflow/transform/z-index影响。 - Transition 做动画:
<Transition name="modal-fade">处理进出场过渡。 v-model控制显隐:用modelValue+update:modelValue,父组件<Modal v-model="show" />一行搞定。- 插槽(slot)解耦内容:
- 默认插槽放主体内容;
#header自定义标题区;#footer自定义底部按钮,并通过插槽 prop 把close暴露给父组件。
- 副作用收敛:
- 打开时锁定
body滚动(document.body.style.overflow = 'hidden'); - 支持点击遮罩关闭(
@click.self)、按Esc关闭(监听 keydown); - 用
watch+onBeforeUnmount在关闭/卸载时清理监听与滚动锁。
- 打开时锁定
- 逻辑复用:开关状态放进
useModal()composable,多个弹窗共享同一套「开/关 + 锁滚动」逻辑。
1 | <Modal v-model="isOpen" title="你好"> |
五、Vue3 为什么用 Proxy 替代 Object.defineProperty?
Options API / Vue2 的 defineProperty 痛点
- 无法监听属性新增/删除:
obj.newKey = 1不会触发响应式,必须Vue.set()。 - 数组索引/长度变化监听不到:
arr[0] = x、arr.length = 0不触发更新。 - 需要递归遍历:初始化时递归
Object.defineProperty每个属性,对象大时初始化慢、且「用不到的属性」也被劫持。 - 对 Map/Set/Class 等支持差。
Vue3 的 Proxy 优势
- 代理整个对象:天然监听新增/删除属性、数组索引与长度变化,无需
Vue.set。 - 惰性响应式:只在「被访问(get)」时才递归代理子对象,初始化更快、更省内存。
- 支持更多数据结构:Map、Set、WeakMap 等。
- 拦截丰富:
get/set/deleteProperty/has/ownKeys等 13 种 trap。
举例对比
1 | // Vue2:新增属性不生效,必须 Vue.set |
1 | // 自己体会 Proxy 的「一次性代理 + 惰性递归」 |
六、Vue3 中 Tree-shaking 的特性(举例)
原理
Vue3 全部用 ES Module 编写,每个 API 都是独立命名的导出(named export)。打包工具(Rollup / Vite / webpack)在做 Tree-shaking 时,只会保留「被 import 并实际使用」的代码,未使用的代码会在生产构建中被摇掉(dead code elimination)。
举例:内置指令按需打包
Vue3 的指令(如 v-model、v-show)在编译后对应独立的运行时函数:
1 | // 你只用了 v-model |
举例:组合式 API 按需引入
1 | // 只引入了你用到的两个 API |
对比 Vue2(为什么 Vue2 难 Tree-shaking)
Vue2 的 API 挂在单一 Vue 构造函数上,是「运行时成员访问」:
1 | // Vue2:Vue.nextTick / Vue.set 是 Vue 对象上的属性 |
而 Vue3 的 import { nextTick } from 'vue' 是静态可分析的命名导入,没用到的 nextTick 就会被摇掉。
直观的体积差异
- 一个只用了
ref+ 模板渲染的最小 Vue3 组件,生产包里只包含「运行时核心 + ref + 渲染相关代码」; - Vue2 即便只用了一个
nextTick,也往往会把整个 Vue 运行时打进去。
这也呼应了「设计目标」里的「更小体积、更好 Tree-shaking」。
七、Vue3 性能提升主要通过哪些方面体现?
- 响应式层(Proxy):惰性代理、免递归初始化、避免
Vue.set,读写拦截更高效。 - 编译层优化:
- 静态提升(hoist static):静态节点/属性只创建一次,不进 render 函数每次执行;
- 补丁标记(patch flag):动态节点打标记,diff 时跳过静态内容,只比对真正变化的部分;
- 事件处理函数缓存:避免每次 render 都生成新函数;
- Block Tree:以动态节点为边界减少 vnode 比对数量。
- Tree-shaking:按需打包,运行时代码更少。
- 更小的运行时核心:相比 Vue2 整体更轻。
- 更优的组件更新粒度:依赖追踪精确到「具体的响应式字段」,组件级更新更精准。
附:本仓库文件地图
http://www.rainxy.com/Vue3Learning/
https://gitee.com/run_yu163/vue3-composition_-api-learing.git
1 | vue3/ |