vue3简易版ref和watch实现

152 阅读5分钟

核心概念

在 Vue.js 中,底层的依赖追踪是通过一个名为「依赖收集器」(Dependency Collector)的机制实现的。依赖收集器负责追踪哪些响应式数据(如响应式对象或计算属性)在当前执行的渲染函数中被访问了,并记录它们的依赖关系。

  • 具体实现包括以下几个核心概念:
  1. Dep(依赖)Dep 是一个类,表示一个可观察的依赖项。每个响应式对象的属性都会有一个关联的 Dep 对象。在依赖收集的过程中,它会记录所有观察者对象。
  2. Watcher(观察者)Watcher 是一个类,用于监听依赖项的变化。当依赖项发生改变时,Watcher 的回调函数就会被调用,从而触发更新。
  3. Track(追踪)Track 函数用来追踪依赖收集过程。当访问一个响应式对象的属性时,Track 函数会被调用。在 Track 函数中,会通过 Dep 类的静态方法 target来获取当前正在执行的Watcher` 实例,并将当前的依赖项添加到它的依赖列表中。
  4. Trigger(触发)Trigger 函数用来触发更新。当依赖项发生变化时,会调用与之关联的 Dep 的 notify 方法,遍历该 Dep 对象的所有观察者,依次调用它们的回调函数。

实现一个简化版的响应式数据系统

ref 的实现

ref 函数的实现。它接受一个初始值 initialValue,并在函数内部定义了一个局部变量 value 来保存数据的值。ref 函数还创建了一个 Dep 的实例对象 dep,用于管理依赖关系。然后,通过返回一个对象字面量的方式,暴露了两个属性 valueset value(newValue),分别用于获取和设置数据的值。 在 get value 的属性访问器中,首先调用 dep.depend() 将当前正在执行的 Watcher 添加到依赖列表中,然后返回 value 的值。在 set value(newValue) 的属性设置器中,首先判断传入的新值 newValue 是否与旧值 value 相等。如果不相等,就将 value 更新为 newValue,然后调用 dep.notify() 触发依赖项的更新。

function ref(initialValue) {
  let value = initialValue;
  const dep = new Dep();

  return {
    get value() {
      // 当访问 ref.value 时,将当前正在执行的 Watcher 添加到依赖中
      dep.depend();
      return value;
    },
    set value(newValue) {
      if (newValue !== value) {
        value = newValue;
        // 触发依赖项的更新
        dep.notify();
      }
    },
  };
}

Dep 构造函数的实现

Dep 类负责管理依赖关系,在其构造函数中初始化了一个 subscribers 数组,用于保存依赖关系(Watcher),Dep 类提供了 depend 方法用于将当前正在执行的 Watcher 添加到依赖列表中,以及 notify 方法用于触发依赖项的更新。

let activeWatcher = null
class Dep {
  constructor() {
    this.subscribers = [];
  }

  depend() {
    if (activeWatcher) {
      this.subscribers.push(activeWatcher);
    }
  }

  notify() {
    this.subscribers.forEach(subscriber => subscriber.update());
  }
}

watch 函数实现

它接受两个参数 targetcallback,其中 target 是一个函数,用于计算所监听的数据,callback 是数据变化时的回调函数。在 watch 函数内部,首先创建一个 Watcher 实例对象 watcher,通过传入的 targetcallback 参数进行初始化。然后调用 watcher.get(),这一步的目的是为了触发一次数据获取,以收集依赖关系。

function watch(target, callback) {
  const watcher = new Watcher(target, callback);
  // 初始触发一次,用于收集依赖
  watcher.get();
}

Watcher 构造函数实现

Watcher 类是实际上的数据监听者,它接受一个 targetcallback 参数并保存起来。在构造函数中,同时调用了 this.get() 方法,这一步的目的是为了初始化 value 属性,并触发一次数据获取以收集依赖关系。 get 方法的作用是获取数据,并将当前 Watcher 实例对象设置为活跃的 activeWatcher,然后调用 target 函数来获取数据的最新值,并将该值保存到 value 属性中,最后再将 activeWatcher 重置为 nullupdate 方法用于更新数据,它首先调用 this.get() 来获取最新的值,并与旧值 this.value 进行比较。如果新值和旧值不相等,就将 value 更新为新值,并调用回调函数 callback 进行通知。

class Watcher {
  constructor(target, callback) {
    this.target = target;
    this.callback = callback;
    this.value = this.get();
  }

  get() {
    // 设置当前活跃的 Watcher 为 this
    activeWatcher = this;
    // 触发ref里面的get
    const value = this.target();
    // 当depend方法执行完成后、重置当前活跃的 Watcher
    activeWatcher = null;
    return value;
  }

  update() {
    const newValue = this.get();
    if (newValue !== this.value) {
      this.value = newValue;
      this.callback(newValue);
    }
  }
}

最后简单的响应式就完成了

这段代码实现了一个简单的响应式数据系统,通过 ref 函数和 watch 函数提供了对数据的监听和响应机制。当依赖的数据发生变化时,会触发相应的回调函数进行处理。

function ref(initialValue) {
  let value = initialValue;
  const dep = new Dep();

  return {
    get value() {
      // 当访问 ref.value 时,将当前正在执行的 Watcher 添加到依赖中
      dep.depend();
      return value;
    },
    set value(newValue) {
      if (newValue !== value) {
        value = newValue;
        // 触发依赖项的更新
        dep.notify();
      }
    },
  };
}
function watch(target, callback) {
  const watcher = new Watcher(target, callback);
  // 初始触发一次,用于收集依赖
  watcher.get();
}

let activeWatcher = null;
class Dep {
  constructor() {
    this.subscribers = [];
  }

  depend() {
    if (activeWatcher) {
      this.subscribers.push(activeWatcher);
    }
  }

  notify() {
    this.subscribers.forEach(subscriber => subscriber.update());
  }
}

class Watcher {
  constructor(target, callback) {
    this.target = target;
    this.callback = callback;
    this.value = this.get();
  }

  get() {
    // 设置当前活跃的 Watcher 为 this
    activeWatcher = this;
    // 触发ref里面的get
    const value = this.target();
    // 当depend方法执行完成后、重置当前活跃的 Watcher
    activeWatcher = null;
    return value;
  }

  update() {
    const newValue = this.get();
    if (newValue !== this.value) {
      this.value = newValue;
      this.callback(newValue);
    }
  }
}



const count = ref(0);
console.log(count);
watch(() => count.value, (newValue) => {
  console.log(`count 的值已改变为:${newValue}`);
});

setTimeout(() => {
    count.value = 1; // 输出:count 的值已改变为:1
}, 5000)