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 ,将改函数赋值给全局变量 activeEffect ,effect 函数的定义,可以不在通过硬编码的格式定义副作用函数,即使是一个匿名函数,也能通过全局变量 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);
    }

    从上述代码可以看出构造数据结构的方式,涉及到了三种数据结构: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 变成了代理对象,很显然,这会在副作用函数与响应式数据之间建立响应联系,从而达到依赖收集的效果

  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++;
    });
相关推荐
farerboy44 分钟前
WEB 项目如何禁用 F12 等功能
前端·vue.js·架构
parade岁月1 小时前
vtable-guild 被收录进 vuejs/awesome-vue 了 🎉
前端·vue.js
沙蒿同学1 小时前
一个人做完一套企业级系统,赚到了第一个 1000 块
vue.js·后端·go
mldong1 小时前
管理后台数据国际化:不建翻译表、一列 JSON、后端零改动
vue.js
Eric的技术杂货铺1 小时前
YsTable 使用:字典管理主从表页面完整实现(列配置、查询、权限按钮、主从联动、增删改查)
前端·vue.js
易朵朵1 小时前
package.json 中的 `vue-router` 详解
前端·vue.js
CopyCode1 小时前
ref、reactive、toRefs 到底该用哪个?我把三个反例都写了一遍
前端·vue.js
用户42528244567502 小时前
Vue3 复杂页面状态怎么管?别什么都往 Pinia 里塞了!
vue.js
牧艺2 小时前
cos-design 4.0:91 个特效组件一次捅成 React / Vue / Web Components / Core
前端·vue.js·web components
ikun_文2 小时前
Vue3框架项目初始化
vue.js·element·vue-router