手写 Vue 源码 === reactive 方法

2 阅读5分钟

手写 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 实现:

  1. 类型检查 : 只对对象类型进行代理
  2. 避免重复代理 : 如果对象已经是响应式的,直接返回
  3. 缓存机制 : 使用 WeakMap 缓存已创建的代理对象,确保同一个原始对象只会有一个对应的代理对象
  4. 创建代理 : 使用 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 的区别
  1. 键的类型
  • WeakMap 只能用对象作为键
  • Map 可以用任何值作为键
  1. 内存管理
  • WeakMap 对键是弱引用,不阻止垃圾回收
  • Map 对键是强引用,会阻止垃圾回收
  1. 功能限制
  • 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;
}
  1. 避免内存泄漏 :当原始对象不再被引用时,WeakMap 中的映射也会被自动清除
  2. 缓存机制 :可以快速查找对象是否已有代理,避免重复创建
  3. 性能优化 :不需要手动管理映射的生命周期,系统会自动清理
  4. WeakMap 是处理对象关联数据时的理想选择,特别是在需要避免内存泄漏的场景中

5. 依赖收集与触发更新

虽然在提供的代码中,依赖收集和触发更新的具体实现被标记为"todo",但我们可以理解其基本原理:

  • 依赖收集 : 当访问响应式对象的属性时,系统会记录当前正在执行的effect函数,建立属性与effect的关联
  • 触发更新 : 当修改响应式对象的属性时,系统会查找与该属性关联的所有effect函数并执行它们

6. 响应式标记

Vue3使用特殊的标记来识别响应式对象:

export enum ReactiveFlags {
  IS_REACTIVE = "__v_isReactive",
}

当我们访问一个对象的 __v_isReactive 属性时,如果它是响应式对象,会返回 true 。这是通过Proxy的get拦截器实现的。

7. 性能优化

Vue3的响应式系统包含多项性能优化:

  1. 避免重复代理 : 检查对象是否已经是响应式的
  2. 代理缓存 : 使用WeakMap缓存已创建的代理对象
  3. 懒代理 : 只有在访问属性时才进行依赖收集

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响应式系统的内部工作机制。