likes
comments
collection
share

vue3响应式原理:被监听的watch

作者站长头像
站长
· 阅读数 18

侦听器指的是,数据发生变化时,会触发被监听的事件(比如:更改 Dom,修改另一些数据等)。以下是watch相关的实现原理:

先看个监听ref类型数据变化的例子:

<template>
  <div>{{ count }}</div>
  <button @click="increaseCount">increaseCount</button>
</template>
<script setup>
import { ref, watch } from "vue";
// 定义ref类型的响应式数据
const count = ref(1);
// 监听数据变化
watch(count, (newValue, oldValue) => {
  console.log(newValue, oldValue);
});
const increaseCount = () => {
  // 修改数据
  count.value = count.value * 2;
};
</script>

一、ref

// ref函数
function ref(value) {
  return createRef(value, false);
}
// createRef函数
function createRef(rawValue, shallow) {
  // 如果是ref,直接返回
  if (isRef(rawValue)) {
    return rawValue;
  }
  // 创建RefImpl实例
  return new RefImpl(rawValue, shallow);
}
// 定义RefImpl类(包含get和set函数)
class RefImpl {
  constructor(value, __v_isShallow) {
    this.__v_isShallow = __v_isShallow;
    this.dep = void 0;
    this.__v_isRef = true;
    this._rawValue = __v_isShallow ? value : toRaw(value);
    this._value = __v_isShallow ? value : toReactive(value);
  }
  // 数据被访问时
  get value() {
    // 依赖收集,当前例子中是收集watch的effect
    trackRefValue(this);
    // 返回响应式数据
    return this._value;
  }
  set value(newVal) {
    const useDirectValue =
      this.__v_isShallow || isShallow(newVal) || isReadonly(newVal);
    newVal = useDirectValue ? newVal : toRaw(newVal);
    // 判断数据是否发生了莲花
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal;
      this._value = useDirectValue ? newVal : toReactive(newVal);
      // 派发更新,当前例子中派发的是watch中的effect
      triggerRefValue(this, 4, newVal);
    }
  }
}

以上是响应式 apiref的基本逻辑,最终是实例化了RefImpl,当访问到该数据时,会通过trackRefValue来实现依赖收集,当修改其数据时,又会通过triggerRefValue的方式进行派发更新。

二、watch

function watch(source, cb, options) {
  // 如果cb不是函数,控制台打印警告
  if (!!(process.env.NODE_ENV !== "production") && !isFunction(cb)) {
    warn$1(
      `\`watch(fn, options?)\` signature has been moved to a separate API. Use \`watchEffect(fn, options?)\` instead. \`watch\` now only supports \`watch(source, cb, options?) signature.`
    );
  }
  return doWatch(source, cb, options);
}
function doWatch(
  source,
  cb,
  { immediate, deep, flush, once, onTrack, onTrigger } = EMPTY_OBJ
) {
  let getter;
  let forceTrigger = false;
  let isMultiSource = false;
  // 1、通过source获取getter
  // source是ref
  if (isRef(source)) {
    getter = () => source.value;
    forceTrigger = isShallow(source);
    // source是reactive
  } else if (isReactive(source)) {
    getter = () => reactiveGetter(source);
    forceTrigger = true;
    // source是数组
  } else if (isArray(source)) {
    isMultiSource = true;
    forceTrigger = source.some((s) => isReactive(s) || isShallow(s));
    getter = () =>
      source.map((s) => {
        if (isRef(s)) {
          return s.value;
        } else if (isReactive(s)) {
          return reactiveGetter(s);
        } else if (isFunction(s)) {
          return callWithErrorHandling(s, instance, 2);
        } else {
          !!(process.env.NODE_ENV !== "production") && warnInvalidSource(s);
        }
      });
    // source是函数
  } else if (isFunction(source)) {
    if (cb) {
      getter = () => callWithErrorHandling(source, instance, 2);
    } else {
      getter = () => {
        if (cleanup) {
          cleanup();
        }
        return callWithAsyncErrorHandling(source, instance, 3, [onCleanup]);
      };
    }
    // 否则为空函数
  } else {
    getter = NOOP;
    !!(process.env.NODE_ENV !== "production") && warnInvalidSource(source);
  }
  if (cb && deep) {
    const baseGetter = getter;
    getter = () => traverse(baseGetter());
  }
  let cleanup;
  let onCleanup = (fn) => {
    cleanup = effect.onStop = () => {
      callWithErrorHandling(fn, instance, 4);
      cleanup = effect.onStop = void 0;
    };
  };
  let oldValue = isMultiSource
    ? new Array(source.length).fill(INITIAL_WATCHER_VALUE)
    : INITIAL_WATCHER_VALUE;
  // 2、定义scheduler
  const job = () => {
    if (!effect.active || !effect.dirty) {
      return;
    }
    if (cb) {
      const newValue = effect.run();
      if (
        deep ||
        forceTrigger ||
        (isMultiSource
          ? newValue.some((v, i) => hasChanged(v, oldValue[i]))
          : hasChanged(newValue, oldValue)) ||
        false
      ) {
        if (cleanup) {
          cleanup();
        }
        callWithAsyncErrorHandling(cb, instance, 3, [
          newValue,
          // pass undefined as the old value when it's changed for the first time
          oldValue === INITIAL_WATCHER_VALUE
            ? void 0
            : isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
            ? []
            : oldValue,
          onCleanup,
        ]);
        oldValue = newValue;
      }
    } else {
      effect.run();
    }
  };
  job.allowRecurse = !!cb;
  let scheduler;
  if (flush === "sync") {
    scheduler = job;
  } else if (flush === "post") {
    scheduler = () => queuePostRenderEffect(job, instance && instance.suspense);
  } else {
    // 用于后续事件优先级的排序(在后续流程中用到,pre表示watch在job.id相同的时候有更高的执行优先级)
    job.pre = true;
    if (instance) job.id = instance.uid;
    scheduler = () => queueJob(job);
  }
  // 3、创建ReactiveEffect实例
  const effect = new ReactiveEffect(getter, NOOP, scheduler);
  const scope = getCurrentScope();
  const unwatch = () => {
    effect.stop();
    if (scope) {
      remove(scope.effects, effect);
    }
  };
  // 4、执行job/effect.run()
  if (cb) {
    if (immediate) {
      job();
    } else {
      // 当前例子中执行effect.run()
      oldValue = effect.run();
    }
  }
  // 5、返回unwatch
  return unwatch;
}

这里主要根据条件获取getterscheduler,然后通过const effect = new ReactiveEffect(getter, NOOP, scheduler)创建侦听器effect,然后,根据immediate执行job()oldValue = effect.run(),最后返回侦听器销毁函数unwatch

这里重点关注oldValue = effect.run(),这里会执行数据实例RefImpl的依赖收集过程。

三、依赖收集

run() {
    this._dirtyLevel = 0;
    if (!this.active) {
      return this.fn();
    }
    let lastShouldTrack = shouldTrack;
    let lastEffect = activeEffect;
    try {
      shouldTrack = true;
      activeEffect = this;
      this._runnings++;
      preCleanupEffect(this);
      return this.fn();
    } finally {
      postCleanupEffect(this);
      this._runnings--;
      activeEffect = lastEffect;
      shouldTrack = lastShouldTrack;
    }
  }

activeEffect = this侦听器effect作为当前激活状态的activeEffect

this.fn()就是getter = () => source.value,结合当前例子,这里访问到了count.value,触发了RefImplget函数。

// get函数
function get() {
  trackRefValue(this);
  return this._value;
}
// 跟踪Ref的值
function trackRefValue(ref2) {
  var _a;
  if (shouldTrack && activeEffect) {
    ref2 = toRaw(ref2);
    trackEffect(
      activeEffect,
      (_a = ref2.dep) != null
        ? _a
        : (ref2.dep = createDep(
            () => (ref2.dep = void 0),
            ref2 instanceof ComputedRefImpl ? ref2 : void 0
          )),
      !!(process.env.NODE_ENV !== "production")
        ? {
            target: ref2,
            type: "get",
            key: "value",
          }
        : void 0
    );
  }
}
// 依赖收集函数
function trackEffect(effect2, dep, debuggerEventExtraInfo) {
  var _a;
  if (dep.get(effect2) !== effect2._trackId) {
    dep.set(effect2, effect2._trackId);
    const oldDep = effect2.deps[effect2._depsLength];
    if (oldDep !== dep) {
      if (oldDep) {
        cleanupDepEffect(oldDep, effect2);
      }
      effect2.deps[effect2._depsLength++] = dep;
    } else {
      effect2._depsLength++;
    }
    if (!!(process.env.NODE_ENV !== "production")) {
      (_a = effect2.onTrack) == null
        ? void 0
        : _a.call(effect2, extend({ effect: effect2 }, debuggerEventExtraInfo));
    }
  }
}

trackRefValue中将activeEffectref2.dep作为参数传入。

trackEffect中将当前激活的effect2作为keyeffect2._trackId作为value收集到dep中。同时,通过effect2.deps[effect2._depsLength++] = dep的方式将dep记录到deps中去。

至此,响应式数据ref和侦听器watch之间的关系完成建立。

run逻辑执行结束后,通过activeEffect = lastEffect将当前激活的effect赋值为undefined

附:在执行render的过程中(已知activeEffect是渲染effect),会访问到count.value,继而收集渲染effect

最终,RefImpl收集了侦听器effect和渲染effect

四、派发更新

当执行数据变化的函数const increaseCount = () => {count.value = count.value * 2;}时,会触发RefImpl中的set函数,进而执行到派发更新的流程triggerRefValue(this, 4, newVal)

function triggerRefValue(ref2, dirtyLevel = 4, newVal) {
  ref2 = toRaw(ref2);
  // 获取收集到的依赖
  const dep = ref2.dep;
  if (dep) {
    triggerEffects(
      dep,
      dirtyLevel,
      !!(process.env.NODE_ENV !== "production")
        ? {
            target: ref2,
            type: "set",
            key: "value",
            newValue: newVal,
          }
        : void 0
    );
  }
}
function triggerEffects(dep, dirtyLevel, debuggerEventExtraInfo) {
  var _a;
  pauseScheduling();
  for (const effect2 of dep.keys()) {
    let tracking;
    if (
      effect2._dirtyLevel < dirtyLevel &&
      (tracking != null
        ? tracking
        : (tracking = dep.get(effect2) === effect2._trackId))
    ) {
      // 判断watch-effect的属性effect2._shouldSchedule是否为真,这里明显为true
      effect2._shouldSchedule ||
        (effect2._shouldSchedule = effect2._dirtyLevel === 0);
      effect2._dirtyLevel = dirtyLevel;
    }
    if (
      effect2._shouldSchedule &&
      (tracking != null
        ? tracking
        : (tracking = dep.get(effect2) === effect2._trackId))
    ) {
      // 这里是空函数,什么也不做
      effect2.trigger();
      if (
        (!effect2._runnings || effect2.allowRecurse) &&
        effect2._dirtyLevel !== 2
      ) {
        effect2._shouldSchedule = false;
        if (effect2.scheduler) {
          // 当前例子中会将watch-effect对应的scheduler推入到queueEffectSchedulers
          queueEffectSchedulers.push(effect2.scheduler);
        }
      }
    }
  }
  resetScheduling();
}

// pauseScheduling
function pauseScheduling() {
  pauseScheduleStack++;
}
// resetScheduling
function resetScheduling() {
  pauseScheduleStack--;
  while (!pauseScheduleStack && queueEffectSchedulers.length) {
    queueEffectSchedulers.shift()();
  }
}

当执行到resetScheduling时,queueEffectSchedulers.shift()()相当于执行了scheduler = () => queueJob(job)

function queueJob(job) {
  if (
    !queue.length ||
    !queue.includes(
      job,
      isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex
    )
  ) {
    if (job.id == null) {
      queue.push(job);
    } else {
      // 根据其id放到一个合适的位置
      queue.splice(findInsertionIndex(job.id), 0, job);
    }
    queueFlush();
  }
}
function queueFlush() {
  if (!isFlushing && !isFlushPending) {
    isFlushPending = true;
    // flushJobs会在下一个异步队列中执行
    currentFlushPromise = resolvedPromise.then(flushJobs);
  }
}
function flushJobs(seen) {
  isFlushPending = false;
  isFlushing = true;
  queue.sort(comparator);
  const check = !!(process.env.NODE_ENV !== "production")
    ? (job) => checkRecursiveUpdates(seen, job)
    : NOOP;
  try {
    for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
      // 获取job
      const job = queue[flushIndex];
      if (job && job.active !== false) {
        // 执行job,具体job的流程请自行断点调试。
        callWithErrorHandling(job, null, 14);
      }
    }
  } finally {
    flushIndex = 0;
    queue.length = 0;
    flushPostFlushCbs(seen);
    isFlushing = false;
    currentFlushPromise = null;
    if (queue.length || pendingPostFlushCbs.length) {
      flushJobs(seen);
    }
  }
}

这里注意事件队列queue的排序规则comparator

const comparator = (a, b) => {
  const diff = getId(a) - getId(b);
  if (diff === 0) {
    if (a.pre && !b.pre) return -1;
    if (b.pre && !a.pre) return 1;
  }
  return diff;
};

这里的pre就印证了watch之前设置的job.pre = true,也说明在job.id相同时,watch事件具有更高的优先级。

至此,我们就简单介绍了watch底层的大体流程,首先,watch它只能被收集,是作为订阅者的形式存在的,既然是订阅者,那么它就需要订阅发布者的消息。当前例子中是用了ref作为发布者,当然也可以用reactive等具有��应式属性的数据。

转载自:https://juejin.cn/post/7393306884538269705
评论
请登录