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

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

深入实现一个 Reactive 响应式

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

    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 实现 用于管理副作用函数的执行,并在数据变化时重新执行这些函数

    // 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),从而实现响应式数据。

    // 实现一个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 来创建

    为什么要使用 Reflect

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

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

    const state = reactive({
        count: 0,
    });
    // 默认先触发一次,初始化渲染
    effect(() => {
        render();
    });
    // 绑定点击事件,点击按钮时,触发了render方法,页面的数据自动变化
    document.getElementById("increment-btn").addEventListener("click", () => {
        state.count++;
    });
    
本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]