手写 Vue 源码 === reactive 方法
目录
[TOC]
Vue3的响应式系统是其核心特性之一,它允许我们以声明式的方式构建交互式用户界面。本文将深入探讨Vue3响应式系统的实现原理,特别是 @vue/reactivity
包的内部工作机制。
1. 响应式系统概述
Vue3的响应式系统主要由以下几个部分组成:
- reactive : 创建响应式对象
- effect : 副作用函数,当依赖的响应式数据变化时自动执行
- baseHandler : 定义Proxy的处理器
这些组件协同工作,构成了一个高效的响应式系统。
2. Proxy与Reflect的应用
Vue3的响应式系统使用ES6的Proxy API来拦截对象的操作,结合Reflect API来执行默认行为。
baseHandler.ts
export enum ReactiveFlags {
IS_REACTIVE = "__v_isReactive",
}
// 「Proxy 需要搭配 Reflect 使用 处理get set」
export const mutableHandlers: ProxyHandler<any> = {
get(target: any, key: any, receiver: any) {
// 如果访问的是代理对象的属性,直接返回
if (key === ReactiveFlags.IS_REACTIVE) {
return true;
}
// 依赖收集 todo...
return Reflect.get(target, key, receiver); // 等价于receiver[key]
},
set(target: any, key: any, value: any, receiver: any) {
// 找到属性 让对应的 effect 执行
// 「触发更新 todo...」
return Reflect.set(target, key, value, receiver);
},
};
这里的 mutableHandlers
定义了如何拦截对象的属性访问和修改操作:
- get : 当访问响应式对象的属性时,除了返回属性值,还会进行依赖收集
- set : 当修改响应式对象的属性时,除了设置新值,还会触发更新
使用 Reflect
而不是直接操作对象的原因是为了保持正确的 this
上下文,特别是在处理getter和setter时。
3. 响应式对象的创建
reactive.ts
import { isObject } from "@vue/shared";
import { mutableHandlers, ReactiveFlags } from "./baseHandler";
// 存储代理对象 {同一个对象,代理对象是唯一的}
const reactiveMap = new WeakMap();
function createReactiveObject(target: any) {
// 只对对象进行代理
if (!isObject(target)) {
return target;
}
// 访问属性 会命中 get 方法 「如果被代理过,一定存在 get 方法」
if (target[ReactiveFlags.IS_REACTIVE]) {
return target;
}
// 如果代理对象已经存在,直接返回 「取缓存」
const existingProxy = reactiveMap.get(target);
if (existingProxy) {
return existingProxy;
}
let proxy = new Proxy(target, mutableHandlers);
//根据 对象缓存代理对象 「存缓存」
reactiveMap.set(target, proxy);
return proxy;
}、
reactive
函数是创建响应式对象的入口,它通过 createReactiveObject
实现:
- 类型检查 : 只对对象类型进行代理
- 避免重复代理 : 如果对象已经是响应式的,直接返回
- 缓存机制 : 使用
WeakMap
缓存已创建的代理对象,确保同一个原始对象只会有一个对应的代理对象 - 创建代理 : 使用
Proxy
和 mutableHandlers 创建代理对象
4. WeakMap的使用
响应式系统使用 WeakMap
而不是普通 Map
来存储原始对象到代理对象的映射:
const reactiveMap = new WeakMap();
WeakMap 是 JavaScript 中的一种特殊集合,与普通 Map 相比有几个关键区别:
主要特点
// 创建 WeakMap
const wm = new WeakMap();
// 只能用对象作为键
const key = {};
wm.set(key, "值");
// 基本用法
wm.get(key); // "值"
wm.has(key); // true
wm.delete(key); // true
WeakMap 与 Map 的区别
- 键的类型 :
- WeakMap 只能用对象作为键
- Map 可以用任何值作为键
- 内存管理 :
- WeakMap 对键是弱引用,不阻止垃圾回收
- Map 对键是强引用,会阻止垃圾回收
- 功能限制 :
- WeakMap 不可遍历,没有 size 属性
- 只有 get、set、has、delete 四个方法
应用场景
// Vue3 响应式系统中的应用
const reactiveMap = new WeakMap();
function reactive(target) {
// 已有代理则直接返回
if (reactiveMap.has(target)) {
return reactiveMap.get(target);
}
// 创建新代理
const proxy = new Proxy(target, handlers);
reactiveMap.set(target, proxy);
return proxy;
}
- 避免内存泄漏 :当原始对象不再被引用时,WeakMap 中的映射也会被自动清除
- 缓存机制 :可以快速查找对象是否已有代理,避免重复创建
- 性能优化 :不需要手动管理映射的生命周期,系统会自动清理
- WeakMap 是处理对象关联数据时的理想选择,特别是在需要避免内存泄漏的场景中
5. 依赖收集与触发更新
虽然在提供的代码中,依赖收集和触发更新的具体实现被标记为"todo",但我们可以理解其基本原理:
- 依赖收集 : 当访问响应式对象的属性时,系统会记录当前正在执行的effect函数,建立属性与effect的关联
- 触发更新 : 当修改响应式对象的属性时,系统会查找与该属性关联的所有effect函数并执行它们
6. 响应式标记
Vue3使用特殊的标记来识别响应式对象:
export enum ReactiveFlags {
IS_REACTIVE = "__v_isReactive",
}
当我们访问一个对象的 __v_isReactive
属性时,如果它是响应式对象,会返回 true
。这是通过Proxy的get拦截器实现的。
7. 性能优化
Vue3的响应式系统包含多项性能优化:
- 避免重复代理 : 检查对象是否已经是响应式的
- 代理缓存 : 使用WeakMap缓存已创建的代理对象
- 懒代理 : 只有在访问属性时才进行依赖收集
8. 与Vue2的对比
Vue3的响应式系统与Vue2相比有显著改进:
- Vue2使用
Object.defineProperty
实现响应式,无法检测对象属性的添加和删除 - Vue3使用Proxy,可以拦截更多的操作,包括属性添加、删除等
- Vue3的响应式系统是独立的包,可以单独使用
9. 实际应用示例
以下是一个简单的示例,展示如何使用Vue3的响应式系统:
安装响应式模块
pnpm install @vue/reactivity -w
reactive
方法会将对象变成 proxy 对象,effect
中使用reactive
对象时会进行依赖收集,稍后属性变化时会重新执行effect
函数~。
<div id="app"></div>
<script type="module">
import {
reactive,
effect
} from '/node_modules/@vue/reactivity/dist/reactivity.esm-browser.js';
const state = reactive({ name: 'erxiao', age: 30 });
effect(() => {
// 副作用函数 默认执行一次,响应式数据变化后再次执行
app.innerHTML = state.name + '今年' + state.age + '岁了';
});
setTimeout(() => {
state.age++;
}, 1000);
</script>
10. 总结
Vue3的响应式系统是一个精心设计的系统,它利用现代JavaScript特性(如Proxy和Reflect)实现了高效的数据响应。通过巧妙的设计,它解决了Vue2中的一些限制,并提供了更好的性能和开发体验。
理解响应式系统的工作原理对于高效使用Vue3和调试复杂应用程序非常重要。希望本文能帮助您更深入地理解Vue3响应式系统的内部工作机制。