当前位置: 首页 > news >正文

手动实现 Vue 3的简易双向数据绑定(模仿源码)

Vue 3 带来了许多令人兴奋的新特性和改进,其中之一就是其双向数据绑定的实现方式。与 Vue 2 使用 Object.defineProperty 不同,Vue 3 利用了 JavaScript 的 Proxy 特性来创建响应式数据。在这篇博客中,我们将探讨 Vue 3 中双向数据绑定的基础原理,并尝试手动实现一个简化版的这一机制。

核心概念

Vue 3 的双向绑定依赖于两个核心概念:响应式代理(Reactive Proxy)Effect 依赖收集系统

响应式代理

Vue 3 使用 Proxy 对象来创建响应式数据。这允许框架在不改变对象本身结构的情况下,拦截并跟踪属性的访问和修改。

Effect 依赖收集系统

Effect 依赖收集系统用于自动追踪响应式数据的使用情况,并在数据变化时重新执行副作用(如渲染函数)。

实现步骤

以下是手动实现 Vue 3 双向绑定的简化步骤:

1. 创建 reactive 函数

这个函数用于将普通对象转换为响应式代理。

function reactive(target) {// 使用 Proxy 对象创建响应式数据return new Proxy(target, {// 拦截对象属性的读取get(target, key, receiver) {console.log(`访问了属性:${key}`);// 使用 Reflect.get 保证 this 指向正确return Reflect.get(target, key, receiver);},// 拦截对象属性的设置set(target, key, value, receiver) {console.log(`设置了属性:${key},新值为:${value}`);// 使用 Reflect.set 设置属性值Reflect.set(target, key, value, receiver);// 此处省略了依赖通知逻辑,实际 Vue 3 中会触发更新return true;}});
}

2. 定义 effect 函数

effect 函数用于注册副作用函数,并在响应式数据变化时执行这些函数。

let activeEffect = null;function effect(fn) {// 设置当前活动的副作用函数activeEffect = fn;// 立即执行函数,用于依赖收集fn();// 重置当前活动的副作用函数activeEffect = null;
}

3. 改进 reactive 以收集依赖

现在我们需要修改 reactive 函数,以支持依赖收集和派发更新。

const targetMap = new WeakMap();function track(target, key) {// 如果没有活动的副作用函数,直接返回if (!activeEffect) return;// 从 targetMap 中获取当前对象的所有依赖let depsMap = targetMap.get(target);if (!depsMap) {// 如果没有,就创建新的 Map 并存入 targetMaptargetMap.set(target, (depsMap = new Map()));}// 获取当前属性的所有依赖let dep = depsMap.get(key);if (!dep) {// 如果没有,就创建新的 Set 并存入 depsMapdepsMap.set(key, (dep = new Set()));}// 将当前活动的副作用函数添加到依赖集合中dep.add(activeEffect);
}function trigger(target, key) {// 从 targetMap 中获取对象的所有依赖const depsMap = targetMap.get(target);if (!depsMap) return;// 获取当前属性的所有依赖let dep = depsMap.get(key);if (dep) {// 对于每一个依赖(即副作用函数),执行它dep.forEach(effect => effect());}
}// 更新 reactive 函数
function reactive(target) {return new Proxy(target, {// 拦截属性读取get(target, key, receiver) {// 收集依赖track(target, key);return Reflect.get(target, key, receiver);},// 拦截属性设置set(target, key, value, receiver) {// 设置属性值Reflect.set(target, key, value, receiver);// 触发更新trigger(target, key);return true;}});
}

4. 测试实例

创建一个响应式对象,并观察它的变化。

const state = reactive({ count: 0 });// 注册一个副作用函数来模拟渲染逻辑
effect(() => {console.log(`count is now: ${state.count}`);
});// 更改响应式数据的属性,触发副作用函数
state.count++;

完整代码

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Vue 3 简易双向数据绑定测试</title>
</head>
<body><h1>Vue 3 简易双向数据绑定测试</h1><div id="app"><p>Count: <span id="count">0</span></p><button id="increment">Increment</button></div><script>// reactive 函数function reactive(target) {const handler = {get(target, key, receiver) {track(target, key);return Reflect.get(target, key, receiver);},set(target, key, value, receiver) {Reflect.set(target, key, value, receiver);trigger(target, key);return true;}};return new Proxy(target, handler);}let activeEffect = null;function effect(fn) {activeEffect = fn;fn();activeEffect = null;}const targetMap = new WeakMap();function track(target, key) {if (!activeEffect) return;let depsMap = targetMap.get(target);if (!depsMap) {targetMap.set(target, (depsMap = new Map()));}let dep = depsMap.get(key);if (!dep) {depsMap.set(key, (dep = new Set()));}dep.add(activeEffect);}function trigger(target, key) {const depsMap = targetMap.get(target);if (!depsMap) return;let dep = depsMap.get(key);if (dep) {dep.forEach(effect => effect());}}// 测试代码const state = reactive({ count: 0 });effect(() => {document.getElementById('count').innerText = state.count;});document.getElementById('increment').addEventListener('click', () => {state.count++;});</script>
</body>
</html>

在这个文件中,我们创建了一个按钮,每当它被点击时,就会增加 state.count 的值。这个值的变化会触发绑定的副作用函数,从而更新显示在页面上的计数结论

小结

以上代码展示了一个 Vue 3 中双向数据绑定的简化实现。通过 Proxy 和动态依赖收集系统,我们可以创建一个灵活且强大的响应式系统。这种实现方式使得 Vue 3 在处理复杂应用时更为高效和灵活。

http://www.lryc.cn/news/263924.html

相关文章:

  • LVS最终奥义之DR直接路由模式
  • t-SNE高维数据可视化实例
  • 配置应用到k8s
  • (四)STM32 操作 GPIO 点亮 LED灯 / GPIO工作模式
  • 你知道跨站脚本攻击吗?一篇带你了解什么叫做XSS
  • JVM入门
  • Cmake基础(5)
  • Rabbitmq 死信取消超时订单
  • C语言—每日选择题—Day55
  • 软件测试岗位的简历怎么写?项目怎么包装
  • 服务器解析漏洞是什么?攻击检测及修复
  • HTML---CSS美化网页元素
  • 【Docker】基础篇
  • Potplayer播放器远程访问群晖WebDav本地资源【内网穿透】
  • 【神经网络】imshow展示图片报错
  • 【C++】对象特性:无参有参构造函数,拷贝构造函数,析构函数
  • 【算法与数据结构】1005、LeetCode K 次取反后最大化的数组和
  • 作业--day34
  • 车辆违规开启远光灯检测系统:融合YOLO-MS改进YOLOv8
  • 爬虫工作量由小到大的思维转变---<第十三章 Scrapy之pipelines分离的思考>
  • 【Unity】运行时创建曲线(贝塞尔的运用)
  • 基于DSP的IIR数字滤波器(论文+源码)
  • Django(一)
  • 微信小程序如何利用createIntersectionObserver实现图片懒加载
  • 七:爬虫-数据解析之正则表达式
  • 云原生之深入解析亿级流量架构之服务限流思路与方法
  • 【Python炫酷系列】祝考研的友友们金榜题名吖(完整代码)
  • KL散度、CrossEntropy详解
  • 【算法】红黑树
  • 2023楚慧杯 WEB方向 部分:(