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++;
    });
相关推荐
bug爱好者7 分钟前
vue3.x 使用vue3-tree-org实现组织架构图 + 自定义模版内容 - 附完整示例
前端·javascript·vue.js
毕设十刻1 小时前
基于Vue的售票系统开发3g480(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js
MC丶科1 小时前
Spring Boot + Vue 实现一个在线商城(商品展示、购物车、订单)!从零到一完整项目
前端·vue.js·spring boot
笙年3 小时前
Vue 基础配置新手总结
前端·javascript·vue.js
哆啦A梦15883 小时前
40 token
前端·vue.js·node.js
摇滚侠3 小时前
Vue 项目实战《尚医通》,获取挂号医生的信息展示,笔记43
前端·javascript·vue.js·笔记·html5
k09334 小时前
vue3中基于AntDesign的Form嵌套表单的校验
前端·javascript·vue.js
茶憶4 小时前
UniApp RenderJS中集成 Leaflet地图,突破APP跨端开发限制
javascript·vue.js·uni-app
N***73854 小时前
前端路由权限动态更新,Vue与React实现
前端·vue.js·react.js
华仔啊4 小时前
Vue3图片放大镜从原理到实现,电商级细节展示方案
前端·vue.js·canvas