请说说你对 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++; });