手动实现 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 并存入 targetMap
        targetMap.set(target, (depsMap = new Map()));
    }
    // 获取当前属性的所有依赖
    let dep = depsMap.get(key);
    if (!dep) {
   
        // 如果没有,就创建新的 Set 并存入 depsMap
        depsMap.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 在处理复杂应用时更为高效和灵活。

相关推荐

  1. 手动实现 Vue 3简易双向数据模仿

    2023-12-21 03:24:04       40 阅读
  2. vue3组件数据双向

    2023-12-21 03:24:04       30 阅读
  3. vue2 双向数据实现及原理

    2023-12-21 03:24:04       12 阅读
  4. Vue双向数据是如何实现

    2023-12-21 03:24:04       10 阅读
  5. VUE3+X6流程图实现数据双向方案

    2023-12-21 03:24:04       21 阅读
  6. Vue双向数据原理

    2023-12-21 03:24:04       45 阅读
  7. Vue 双向数据

    2023-12-21 03:24:04       19 阅读
  8. vue2解析---v-model双向数据

    2023-12-21 03:24:04       38 阅读

最近更新

  1. TCP协议是安全的吗?

    2023-12-21 03:24:04       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-21 03:24:04       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-21 03:24:04       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-21 03:24:04       20 阅读

热门阅读

  1. uniapp-使用返回的base64转换成图片

    2023-12-21 03:24:04       40 阅读
  2. spring 配置模型

    2023-12-21 03:24:04       38 阅读
  3. Python_Tkinter和OpenCV模拟行星凌日传输光度测定

    2023-12-21 03:24:04       47 阅读
  4. 12.1 知识回顾(过滤器、 模型层)

    2023-12-21 03:24:04       28 阅读
  5. 进制转换和图像处理的编程实现

    2023-12-21 03:24:04       35 阅读
  6. vim 基本命令查找和替换

    2023-12-21 03:24:04       40 阅读
  7. 安全面试总结

    2023-12-21 03:24:04       40 阅读
  8. LVS+Keepalived 高可用群集

    2023-12-21 03:24:04       36 阅读
  9. 驱动学习篇

    2023-12-21 03:24:04       40 阅读
  10. 3.9【窗口】窗口使用示例(窗口缩放 二)

    2023-12-21 03:24:04       36 阅读