Vue源码学习(watch)

326 阅读3分钟

前言

这一节讲一下 watchwatchEffect 函数的实现。

watch & watchEffect

export function watch(source, cb: Function, options = {} as any) {
  return doWatch(source, cb, options);
}

export function watchEffect(getter: Function, options = {} as any) {
  return doWatch(getter, undefined, options);
}

watchwatchEffect 函数的实现都调用了 doWatch 函数。

只不过 watch 函数在 dowatch 函数中传入了第二个 cb 参数,watchEffect 函数则给第二个参数传了 undefined

接下来我们看一下,doWatch 函数是如何实现的:

function doWatch(source, cb: Function, { deep = false, immidiate }) {
  const reactiveGetter = source => traverse(source, deep === false ? 1 : void 0);

  let getter;

  if (isReactive(source)) {
    // 如果是响应性对象,循环遍历每个 key 都会触发 getter 函数, 进而出发依赖收集
    getter = () => reactiveGetter(source);
  } else if (isRef(source)) {
    // 如果是 ref 对象,获取 value 值, 也会触发依赖收集
    getter = () => source.value;
  } else if (isFunction(source)) {
    // 如果是函数,直接赋值给 getter
    getter = source;
  }

  let oldValue;

  const job = () => {
    const newValue = effect.run();
    cb && cb(newValue, oldValue);
    oldValue = newValue;
  };

  const effect = new ReactiveEffect(getter, job);
  oldValue = effect.run();

  // 立即执行一次回调
  if (immidiate) {
    cb && cb(oldValue, undefined);
  }

  const unwatch = () => {
    effect.stop();
  };

  return unwatch;
}

dowatch 接收三个参数,第一个参数是响应性变量或者是一个 effect 函数,第二个参数是一个回调函数,第三个参数是一个选项对象。

dowatch 函数首先创建了一个 reactiveGetter 函数,这个函数通过调用 traverse 函数来创建响应性 getter 函数。

// 遍历对象的每个 key 就会触发 这个对象的 getter 函数
function traverse(source, depth, currentDepth = 0, seen = new Set()) {
  if (!isObject(source)) {
    return source;
  }

  // 控制遍历深度
  if (depth) {
    if (currentDepth >= depth) {
      return source;
    }
    currentDepth++;
  }

  // 防止循环遍历
  if (seen.has(source)) {
    return source;
  }
  seen.add(source);

  for (const key in source) {
    traverse(source[key], depth, currentDepth, seen);
  }

  return source;
}

traverse 函数的作用就是遍历对象,如果 deeptrue,则会递归遍历所有子属性。

遍历对象的作用就是为了触发这个对象上的属性的 getter 方法,进而触发依赖收集。

然后,创建一个 getter 变量,通过判断传进来的 source 来给 getter 赋值:

  1. 如果 source 是响应性对象,则将 reactiveGetter 赋值给 getter
  2. 如果 sourceref 对象,则创建一个函数,去读取 source.value,再将函数赋值给 getter
  3. 如果 source 是函数,则直接赋值给 getter

其实看到这,我们就能明白了,为什么在监听一个 ref 对象时,可以直接监听这个对象,也可以传入一个函数,但函数内部必须要读取这个 ref 对象的 value 值。因为要触发依赖收集。

然后,创建一个 oldValue 变量,用来存放旧值。

同时创建一个 job 函数,用来作为 effectscheduler 函数。

const job = () => {
  const newValue = effect.run();
  cb && cb(newValue, oldValue);
  oldValue = newValue;
};

job 函数,首先会执行一遍 effectrun 方法,获取新值,然后调用回调函数,并传入新值和旧值。然后,将新值赋值给 oldValue

然后创建一个 effect 对象,并传入 getter 函数和 job 函数。并执行一遍 effectrun 方法,获取旧值。

接下来就是 判断 有没有传入 immediate 选项,如果有,则立即执行一次回调函数。

最后,创建一个 unwatch 函数,用来停止依赖收集,并返回这个 unwatch 函数。

至此,watchwatchEffect 函数的实现就讲完了。