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++;
    });
相关推荐
幻云20105 小时前
Python深度学习:从筑基到登仙
前端·javascript·vue.js·人工智能·python
爱吃泡芙的小白白7 小时前
Vue 3 核心原理与实战:从响应式到企业级应用
前端·javascript·vue.js
JosieBook10 小时前
【Vue】12 Vue技术—— Vue 事件修饰符详解:掌握事件处理的高级技巧
前端·javascript·vue.js
刘一说12 小时前
Vue3 模块语法革命:移除过滤器(Filters)的深度解析与迁移指南
前端·vue.js·js
Trae1ounG12 小时前
这是什么dom
前端·javascript·vue.js
5134959212 小时前
在Vue.js项目中使用docx和file-saver实现Word文档导出
前端·vue.js·word
请叫我聪明鸭14 小时前
基于 marked.js 的扩展机制,创建一个自定义的块级容器扩展,让内容渲染为<div>标签而非默认的<p>标签
开发语言·前端·javascript·vue.js·ecmascript·marked·marked.js插件
RunsenLIu15 小时前
基于Spring Boot + Vue的图书馆座位预约管理系统
vue.js·spring boot·后端
Trae1ounG16 小时前
Vue生命周期
前端·javascript·vue.js