请说说你对 Vue3 响应式的理解
Vue 的数据响应式,是当数据变化时,自动执行依赖于它的副作用函数。
深入实现一个 Reactive 响应式
通过
effect函数来监控数据变化,并在数据变化时调用render函数自动更新视图。
-
首先需要一个依赖追踪系统,用于依赖追踪,并在数据变化时通知相关的副作用函数重新执行。
jsclass Dep { constructor() { // 用 Set 来存储订阅的副作用函数,避免重复添加 this.subscribers = new Set(); } // 添加依赖 depend() { // activeEffect 是当前正在执行的副作用函数 if (activeEffect) { this.subscribers.add(activeEffect); } } // 通知所有依赖更新 notify() { this.subscribers.forEach((effect) => effect.update()); } }activeEffect是一个全局变量,用于存储被注册的副作用函数,在下文的执行effect函数执行前赋值。 -
Watcher实现 用于管理副作用函数的执行,并在数据变化时重新执行这些函数js// activeEffect 用于存储当前正在执行的副作用函数 let activeEffect = null; class Watcher { constructor(effect) { this.effect = effect; this.run(); // 初始化时执行一次副作用函数 } // 执行副作用函数 run() { activeEffect = this; this.effect(); activeEffect = null; } // 数据变化时调用,重新执行副作用函数 update() { this.run(); } } // 定义一个 effect 函数,用于注册副作用函数 function effect(fn) { new Watcher(fn); }effect注册副作用函数,通过实例化Watcher,将改函数赋值给全局变量activeEffect,effect函数的定义,可以不在通过硬编码的格式定义副作用函数,即使是一个匿名函数,也能通过全局变量activeEffect来找到并执行。 -
响应式数据实现 使用
Proxy来拦截对对象属性的访问(get)和修改(set),从而实现响应式数据。js// 实现一个reactive函数,将一个普通对象转换为响应式对象 // 使用 WeakMap 来存储对象及其属性对应的 Dep const targetMap = new WeakMap(); // 获取对象属性对应的 Dep function getDep(target, key) { let depsMap = targetMap.get(target); // 如果没有对应的依赖 Map,则创建一个新的 Map if (!depsMap) { depsMap = new Map(); targetMap.set(target, depsMap); } let dep = depsMap.get(key); // 如果没有对应的 Dep,则创建一个新的 Dep if (!dep) { dep = new Dep(); depsMap.set(key, dep); } return dep; } // 创建响应式对象 function reactive(target) { const handler = { get(target, key, receiver) { // 拿到对应的 Dep const dep = getDep(target, key); dep.depend(); // 追踪依赖 return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { const result = Reflect.set(target, key, value, receiver); const dep = getDep(target, key); dep.notify(); // 通知依赖更新 return result; }, }; return new Proxy(target, handler); }从上述代码可以看出构造数据结构的方式,涉及到了三种数据结构:
WeakMap,Map,Set。WeakMap由target --> Map构成;Map由key --> Set构成;
其中
WeakMap的键是原始对象target,WeakMap的值是一个Map实例,而Map的键是原始对象target中的key,Map的值是一个由副作用函数组成的Set。为什么要使用
WeakMap来创建?WeakMap的key是弱引用,它不影响垃圾回收器的工作,一旦它的key被垃圾回收器回收了,那么对应的键和值就访问不到了为什么要使用
Reflect?实际上,
Reflect的get,set方法还接受第三个参数Receiver(指定接收者),可以理解为函数调用中的this,因此在触发get,set时,能准确的将代理对象作为this传递给get,set方法
this由原始对象target变成了代理对象,很显然,这会在副作用函数与响应式数据之间建立响应联系,从而达到依赖收集的效果 -
更新
dom视图,需要一个render函数来更新视图,当响应式数据发生变化时,自动调用render函数更新视图html<div id="app"> <div id="count-display"></div> <button id="increment-btn">Increment</button> </div>jsfunction render() { document.getElementById( "count-display" ).innerText = `Count: ${state.count}`; } -
综合应用
jsconst state = reactive({ count: 0, }); // 默认先触发一次,初始化渲染 effect(() => { render(); }); // 绑定点击事件,点击按钮时,触发了render方法,页面的数据自动变化 document.getElementById("increment-btn").addEventListener("click", () => { state.count++; });