第 91 期 - Vue3 的架构改进与响应式原理剖析
摘要
本文对比了 Vue2 和 Vue3,介绍了 Vue3 在结构、模块管理、打包方面的改进,阐述其设计思想,还深入剖析了 Vue3 中 Reactivity 模块的响应式源码,包括 reactive、effect 等的实现以及依赖收集和触发机制。
一、Vue3 结构分析
1. Vue2 与 Vue3 的对比
- 在对 TypeScript 支持方面,Vue2 不友好,因为所有属性都放在 this 对象上,难以推导组件数据类型。
- Vue2 大量 API 挂载在 Vue 对象原型上,难以实现 TreeShaking。
- 架构层面,Vue2 对跨平台 dom 渲染开发支持不佳,而 Vue3 允许自定义渲染器,扩展能力强。
- Vue3 受 ReactHook 启发有了 CompositionAPI,并且对虚拟 DOM 重写、模板编译优化。
2. Vue3 设计思想
- 模块拆分:Vue3 更注重模块拆分,Vue2 中无法单独使用部分模块,而 Vue3 模块间耦合度低可独立使用。例如只想用响应式部分,在 Vue2 需引入完整 Vuejs,Vue3 则不需要。
- 按需引入:Vue2 中很多方法和组件即使未使用也会被打包,Vue3 通过构建工具 Tree - shaking 机制实现按需引入,减少打包体积。
- 自定义渲染器:Vue3 允许自定义渲染器,扩展方便,改写 Vue 源码改造渲染方式,同时保留了 Vue2 的一些特点,如依旧是声明式框架、采用虚拟 DOM、区分编译时和运行时等。简单来说,Vue3 框架更小且扩展更方便。
3. monorepo 管理项目
- Monorepo 是在一个项目仓库管理多个模块/包的代码管理模式,Vue3 内部实现模块拆分,源码采用 Monorepo 方式管理,将模块拆分到 package 目录。
- 这种方式有诸多优点,一个仓库可维护多个模块,方便版本管理和依赖管理,模块间引用和调用方便,每个包可独立发布。
- 早期使用 yarn workspace + lerna 管理项目,现在是 pnpm。pnpm 是快速且节省磁盘空间的包管理器,采用符号链接管理模块。
- pnpm 的特点:
- 快速高效:内部使用基于内容寻址的文件系统存储磁盘文件,不会重复安装同一个包,不同版本也会极大程度复用之前版本代码。
- 支持 Monorepo:这是与 npm/yarn 很大的不同点。
- 安全性高:自创依赖管理方式,解决了 npm/yarn 中存在的非法访问问题。
- pnpm 的安装和初始化:
- 全局安装(node 版本>16):
npm install pnpm -g
。 - 初始化:
pnpm init
。 - 配置 workspace:在根目录创建
pnpm - workspace.yaml
,将packages
下所有目录作为包管理,搭建 Monorepo。
- 全局安装(node 版本>16):
4. 项目结构
- 介绍了
packages
下各个模块的功能,如reactivity
是响应式系统,runtime - core
是与平台无关的运行时核心等。 - 还介绍了包的相互依赖,例如把
packages/shared
安装到packages/reactivity
:pnpm install @vue/shared@workspace --filter @vue/reactivity
,以及在reactivity/src/computed.ts
中引入shared
中相关方法的示例。 - 提及了
@vue/shared
引入可能会报错,需要在tsconfig.json
中进行配置。
5. 打包
- 所有包的入口均为
src/index.ts
以实现统一打包。 - 以
reactivity/package.json
和shared/package.json
为例,介绍了package.json
中的一些配置,如name
、version
、main
、module
、unpkg
、buildOptions
等。 - 开发环境 esbuild 打包:开发时执行脚本,参数为要打包的模块,如
"scripts": {"dev": "node scripts/dev.js reactivity -f global"}
。同时给出了相关的 esbuild 打包代码逻辑,包括如何处理输出、外部依赖等情况。 - 生产环境 rollup 打包:具体代码参考
rollup.config.mjs
和build.js
。
二、Vue3 中 Reactivity 模块
1. vue3 对比 vue2 的响应式变化
- 在 Vue2 中使用
defineProperty
进行数据劫持,存在性能差、新增和删除属性无法监控变化(需通过$set
、$delete
实现)、数组单独处理等问题。 - Vue3 使用
Proxy
实现响应式数据变化,解决了上述问题。
2. CompositionAPI
- 在 Vue2 中采用 OptionsAPI,用户提供多种属性,编写复杂业务逻辑时会有反复横跳问题,所有属性通过 this 访问存在指向明确问题,未使用的方法或属性依旧会被打包,全局 API 都在 Vue 对象上公开。
- Composition API 对 tree - shaking 更友好,代码更容易压缩,提取公共逻辑方便,解决了 Vue2 中 mixins 的一些问题,简单组件可采用 OptionsAPI,复杂逻辑中 CompositionAPI 优势明显。
3. 基本使用
给出了reactive
和effect
的基本使用示例:
const { effect, reactive } = VueReactivity;
const state = reactive({name: 'qpp', age:18, address: {city: '南京'}});
console.log(state.address);
effect(()=>{
console.log(state.name)
});
4. reactive 实现
- 首先有
reactive
函数,在函数内部,如果要观察的是只读代理则直接返回,否则调用createReactiveObject
函数。
export function reactive(target: object) {
if (isReadonly(target)) {
return target;
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
);
}
createReactiveObject
函数中,如果目标不是对象则直接返回,否则创建代理对象并返回。
function createReactiveObject(target, baseHandler) {
if (!isObject(target)) {
return target;
}
const observed = new Proxy(target, baseHandler);
return observed;
}
- 还介绍了
baseHandlers
中的get
和set
方法代码,get
方法用于获取属性时的操作,如对获取的值进行反射,如果是对象类型则返回当前对象的代理对象;set
方法用于修改属性时的操作,如判断属性是否新增、值是否修改等情况。
5. effect 实现
- 首先介绍了
effect
的一些基本原理,如依赖收集是借助 js 单线程特点,默认调用effect
时会调用proxy
的get
,让属性记住依赖的effect
,effect
也记住对应的属性,数据结构使用weakMap
。 - 有
cleanEffect
函数用于清理effect
中存入属性中的set
中的effect
。 effect
函数将用户传递的函数变为响应式的effect
,创建ReactiveEffect
实例并执行,返回runner
,用户可手动调用runner
重新执行。
export function effect<T = any>(
fn: () => T,
options?: ReactiveEffectOptions
) {
const _effect = new ReactiveEffect(fn, options.scheduler);
_effect.run();
const runner = _effect.run.bind(_effect);
runner.effect = _effect;
return runner;
}
ReactiveEffect
类中有active
、parent
、deps
等属性,run
方法用于依赖收集和执行用户传入的函数,stop
方法用于停止effect
。
6. 依赖收集
- 核心代码有
track
和trigger
函数,track
用于收集属性对应的effect
,trigger
用于触发属性对应effect
执行。 - 在
createGetter
和createSetter
函数中分别调用track
和trigger
,例如get
方法中取值时进行依赖收集,set
方法中设置值时触发更新。 - 详细介绍了
track
和trigger
的实现代码,track
中涉及到targetMap
(WeakMap
类型),根据目标、类型、键等信息进行依赖收集相关操作;trigger
中根据目标获取依赖映射,根据键和类型执行对应的effect
。
扩展阅读
Made by 捣鼓键盘的小麦 / © 2025 Front Talk 版权所有