Vue3 响应式中的 Reactive

请说说你对 Vue3 响应式的理解

Vue 的数据响应式,是当数据变化时,自动执行依赖于它的副作用函数

深入实现一个 Reactive 响应式

通过 effect 函数来监控数据变化,并在数据变化时调用 render 函数自动更新视图。

  1. 首先需要一个依赖追踪系统,用于依赖追踪,并在数据变化时通知相关的副作用函数重新执行。

    js 复制代码
    class Dep {
      constructor() {
        // 用 Set 来存储订阅的副作用函数,避免重复添加
        this.subscribers = new Set();
      }
      // 添加依赖
      depend() {
        // activeEffect 是当前正在执行的副作用函数
        if (activeEffect) {
          this.subscribers.add(activeEffect);
        }
      }
      // 通知所有依赖更新
      notify() {
        this.subscribers.forEach((effect) => effect.update());
      }
    }

    activeEffect 是一个全局变量,用于存储被注册的副作用函数,在下文的执行 effect 函数执行前赋值。

  2. 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 ,将改函数赋值给全局变量 activeEffecteffect 函数的定义,可以不在通过硬编码的格式定义副作用函数,即使是一个匿名函数,也能通过全局变量 activeEffect 来找到并执行。

  3. 响应式数据实现 使用 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);
    }

    从上述代码可以看出构造数据结构的方式,涉及到了三种数据结构:WeakMapMapSet

    • WeakMaptarget --> Map 构成;
    • Mapkey --> Set 构成;

    其中 WeakMap 的键是原始对象 targetWeakMap 的值是一个 Map 实例,而 Map 的键是原始对象 target 中的 keyMap 的值是一个由副作用函数组成的 Set

    为什么要使用 WeakMap 来创建

    WeakMapkey 是弱引用,它不影响垃圾回收器的工作,一旦它的 key 被垃圾回收器回收了,那么对应的键和值就访问不到了

    为什么要使用 Reflect

    实际上,Reflectgetset 方法还接受第三个参数 Receiver(指定接收者),可以理解为函数调用中的 this,因此在触发 getset 时,能准确的将代理对象作为 this 传递给 getset 方法
    this 由原始对象 target 变成了代理对象,很显然,这会在副作用函数与响应式数据之间建立响应联系,从而达到依赖收集的效果

  4. 更新 dom 视图,需要一个 render函数来更新视图,当响应式数据发生变化时,自动调用 render 函数更新视图

    html 复制代码
    <div id="app">
      <div id="count-display"></div>
      <button id="increment-btn">Increment</button>
    </div>
    js 复制代码
    function render() {
        document.getElementById(
          "count-display"
        ).innerText = `Count: ${state.count}`;
    }
  5. 综合应用

    js 复制代码
    const state = reactive({
        count: 0,
    });
    // 默认先触发一次,初始化渲染
    effect(() => {
        render();
    });
    // 绑定点击事件,点击按钮时,触发了render方法,页面的数据自动变化
    document.getElementById("increment-btn").addEventListener("click", () => {
        state.count++;
    });
相关推荐
paopaokaka_luck3 小时前
基于SpringBoot+Vue的志行交通法规在线模拟考试(AI问答、WebSocket即时通讯、Echarts图形化分析、随机测评)
vue.js·人工智能·spring boot·后端·websocket·echarts
程序定小飞3 小时前
基于springboot的蜗牛兼职网的设计与实现
java·数据库·vue.js·spring boot·后端·spring
我的写法有点潮6 小时前
彻底理解 JavaScript 的深浅拷贝
前端·javascript·vue.js
柯南二号7 小时前
【大前端】Vue 和 React 的区别详解 —— 两大前端框架深度对比
前端·vue.js·前端框架
weixin_446938878 小时前
uniapp vue-i18n如何使用
前端·vue.js·uni-app
csgo打的菜又爱玩12 小时前
Vue 基础(实战模板与命名指南)
前端·javascript·vue.js
gerrgwg14 小时前
Vue-library-start,一个基于Vite的vue组件库开发模板
前端·javascript·vue.js
星晨雪海18 小时前
怎么格式化idea中的vue文件
前端·vue.js·intellij-idea