vue3.5.18源码:一文搞懂ref、computed、watch和render底层关系网

首先我们罗列一下响应式相关的核心功能:

  • ref,数据变化时,对应的 computedwatchrender都会更新
  • computed,数据变化时,对应的 watchrender会更新
  • watch,会根据 ref 的数据变化来执行副作用 函数
  • render,会根据 refcomputed 的数据变化来重新渲染视图

看完本文,会梳理明白以上核心功能底层是如何实现的,以及它们之间的关系:

  • Dep核心类,RefImplComputedRefImpl的实例均依赖于Dep类进行依赖管理
  • ReactiveEffect核心类,watchrender的底层逻辑中,订阅者功能由ReactiveEffect类实现

接下来,我们开始探索之旅吧!

ts 复制代码
<div id="app"></div>
<script>
    const App = {
        template:`{{ count }}*2 = {{ computedCount }}<button @click="plus">plus</button>
        `,
        setup() {
            // 定义响应式数据
            debugger;
            console.log('setup');
            const count = Vue.ref(0);
            // 定义计算属性
            debugger;
            const computedCount = Vue.computed(() => {
                debugger;
                console.log('computed');
                return count.value * 2;
            })
            // 定义侦听器-监听count
            debugger
            Vue.watch(count, (newValue, oldValue) => {
                debugger;
                console.log('watch count');
                console.log(newValue, oldValue);
            })
            // 定义侦听器-监听computedCount
            debugger
            Vue.watch(computedCount, (newValue, oldValue) => {
                debugger;
                console.log('watch computedCount');
                console.log(newValue, oldValue);
            })
            // 定义修改数据的方法
            const plus = () => {
                debugger;
                count.value++;
            }
            // 暴露给模板
            return {
                count,
                computedCount,
                plus,
            };
        }
    };
    // 创建应用并挂载
    const app = Vue.createApp(App);
    app.mount("#app");
</script>

一、发布订阅者模式-发布者类

1、RefImpl

我们从const count = Vue.ref(0)开始,一步步寻找RefImpl类:

ts 复制代码
// ref函数
function ref(value) {
  return createRef(value, false);
}
// createRef函数
function createRef(rawValue, shallow) {
  if (isRef(rawValue)) {
    return rawValue;
  }
  return new RefImpl(rawValue, shallow);
}
// RefImpl类
class RefImpl {
  constructor(value, isShallow2) {
    // 依赖管理
    this.dep = new Dep();
    this["__v_isRef"] = true;
    this["__v_isShallow"] = false;
    this._rawValue = isShallow2 ? value : toRaw(value);
    this._value = isShallow2 ? value : toReactive(value);
    this["__v_isShallow"] = isShallow2;
  }
  get value() {
    {
      // 在访问RefImpl实例化的对象的值的时候,会进行依赖收集
      this.dep.track({
        target: this,
        type: "get",
        key: "value",
      });
    }
    return this._value;
  }
  set value(newValue) {
    const oldValue = this._rawValue;
    const useDirectValue =
      this["__v_isShallow"] || isShallow(newValue) || isReadonly(newValue);
    newValue = useDirectValue ? newValue : toRaw(newValue);
    if (hasChanged(newValue, oldValue)) {
      this._rawValue = newValue;
      this._value = useDirectValue ? newValue : toReactive(newValue);
      {
        // 在修改RefImpl实例化的对象的值的时候,会执行派发更新
        this.dep.trigger({
          target: this,
          type: "set",
          key: "value",
          newValue,
          oldValue,
        });
      }
    }
  }
}

当执行到const count = Vue.ref(0)时,会执行响应式数据初始化函数,返回一个RefImpl类的实例化对象。

RefImpl类定义了取值和赋值两个方法:getset, 在访问它时,会执行get函数,进而执行this.dep.track收集依赖。在数据变化时,会执行set函数,进而执行this.dep.trigger派发更新。

我们发现,RefImplgetset方法,都依赖了Dep类的实例化对象this.dep,所以RefImplDep类是紧密相关的。

Dep的实现留个悬念,我们继续看ComputedRefImpl类。

2、ComputedRefImpl

我们从Vue.computed(() => { return count.value * 2; })开始,寻找ComputedRefImpl类:

ts 复制代码
// computed函数
const computed = (getterOrOptions, debugOptions) => {
  const c = computed$1(getterOrOptions, debugOptions, isInSSRComponentSetup);
  {
    const i = getCurrentInstance();
    if (i && i.appContext.config.warnRecursiveComputed) {
      c._warnRecursive = true;
    }
  }
  return c;
};
// computed$1函数
function computed$1(getterOrOptions, debugOptions, isSSR = false) {
  let getter;
  let setter;
  if (isFunction(getterOrOptions)) {
    // 如果是函数,直接赋值为getter
    getter = getterOrOptions;
  } else {
    // 如果是对象,直接获取getterOrOptions中的get
    getter = getterOrOptions.get;
    setter = getterOrOptions.set;
  }
  // 实例化ComputedRefImpl类
  const cRef = new ComputedRefImpl(getter, setter, isSSR);
  if (debugOptions && !isSSR) {
    cRef.onTrack = debugOptions.onTrack;
    cRef.onTrigger = debugOptions.onTrigger;
  }
  return cRef;
}
// ComputedRefImpl类
class ComputedRefImpl {
  constructor(fn, setter, isSSR) {
    // 这里的fn就是传入的参数getter
    this.fn = fn;
    this.setter = setter;
    this._value = void 0;
    // 以自身实例为参数,作为Dep的参数
    this.dep = new Dep(this);
    this.__v_isRef = true;
    this.deps = void 0;
    this.depsTail = void 0;
    this.flags = 16;
    this.globalVersion = globalVersion - 1;
    this.next = void 0;
    this.effect = this;
    this["__v_isReadonly"] = !setter;
    this.isSSR = isSSR;
  }
  // notify函数
  notify() {
    this.flags |= 16;
    if (!(this.flags & 8) && activeSub !== this) {
      batch(this, true);
      return true;
    }
  }
  // get函数
  get value() {
    // 收集订阅者(渲染函数),建立`computed`和`render`之间的关系
    const link = this.dep.track({
      target: this,
      type: "get",
      key: "value",
    });
    // 将自己作为订阅者,让`count`订阅,建立`computed`和`count`之间的关系
    refreshComputed(this);
    if (link) {
      link.version = this.dep.version;
    }
    return this._value;
  }
  // set函数
  set value(newValue) {
    if (this.setter) {
      this.setter(newValue);
    } else {
      warn$2("Write operation failed: computed value is readonly");
    }
  }
}

当执行到const computedCount = Vue.computed(() => { return count.value * 2; })时,会执行响应式初始化函数,返回一个ComputedRefImpl类的实例化对象。

ComputedRefImpl类定义了取值和赋值两个方法:getset, 在访问到计算属性时,会执行到get函数,进而执行this.dep.track收集依赖。

也可通过给computed传递getset的方式,自定义set函数。此时可以直接修改计算属的值,进而执行this.setter(newValue)函数(这里不是主线逻辑,暂不展开)。

我们发现,ComputedRefImplget方法,依赖了Dep类的实例化对象this.dep,所以ComputedRefImplDep类也是紧密相关的。

接下来,是时候看看Dep类的实现了。

3、Dep

ts 复制代码
// Dep类,作为发布者和订阅者之间的关系管理类
class Dep {
  constructor(computed) {
    this.computed = computed;
    this.version = 0;
    // 核心: dep和当前活跃的sub之间的关系
    this.activeLink = void 0;
    this.subs = void 0;
    this.map = void 0;
    this.key = void 0;
    // 订阅者个数
    this.sc = 0;
    this.__v_skip = true;
    {
      this.subsHead = void 0;
    }
  }
  track(debugInfo) {
    // 依赖收集,稍后介绍...
  }
  trigger(debugInfo) {
    // 派发更新,稍后介绍...
  }
  notify(debugInfo) {
    // 通知更新,稍后介绍...
  }
}

以上就是Dep类的实现,它定义了tracktriggernotify三个方法,分别对应依赖收集、派发更新和通知更新,这个我们在发布者和订阅者构建关系的部分详细介绍。

到这里我们已经了解了RefImpl类、ComputedRefImpl类和Dep类之间的依赖关系。

RefImpl类和ComputedRefImpl类对于订阅者的收集都是通过Deptrack方法来实现的。

二、发布订阅者模式-订阅者类

1、从watch中寻找ReactiveEffect

我们从Vue.watch(() => count.value, (newValue, oldValue) => { console.log(newValue, oldValue); })开始寻找ReactiveEffect类:

ts 复制代码
// 1、 `watch`函数首先判断`cb`是否是函数,如果不是函数,则会发出警告,然后调用`doWatch`函数。
function watch(source, cb, options) {
  // cb必须是函数,否则会有警告提示
  if (!isFunction(cb)) {
    warn$1("省略提示内容");
  }
  return doWatch(source, cb, options);
}
// 2、`doWatch`函数首先判断传入的参数,根据不同的情况进行警告提示。然后构建`watch`参数,最后调用`watch$1`函数。
function doWatch(source, cb, options = EMPTY_OBJ) {
  // options中的参数
  const { immediate, deep, flush, once } = options;
  if (!cb) {
    // 省略提示内容
  }
  // 构建watch参数
  const baseWatchOptions = extend({}, options);
  baseWatchOptions.onWarn = warn$1;
  const instance = currentInstance;
  baseWatchOptions.call = (fn, type, args) =>
    callWithAsyncErrorHandling(fn, instance, type, args);
  let isPre = false;
  if (flush === "post") {
    baseWatchOptions.scheduler = (job) => {
      queuePostRenderEffect(job, instance && instance.suspense);
    };
  } else if (flush !== "sync") {
    isPre = true;
    // scheduler函数
    baseWatchOptions.scheduler = (job, isFirstRun) => {
      if (isFirstRun) {
        job();
      } else {
        queueJob(job);
      }
    };
  }
  // augmentJob函数
  baseWatchOptions.augmentJob = (job) => {
    if (cb) {
      job.flags |= 4;
    }
    if (isPre) {
      job.flags |= 2;
      if (instance) {
        job.id = instance.uid;
        job.i = instance;
      }
    }
  };
  // 执行watch$1函数
  const watchHandle = watch$1(source, cb, baseWatchOptions);
  return watchHandle;
}

// 3、`watch$1`函数中主要构建`getter`和`setter`函数,实例化`ReactiveEffect`得到`effect`,构建`job`函数。
function watch$1(source, cb, options = EMPTY_OBJ) {
  const { immediate, deep, once, scheduler, augmentJob, call } = options;
  // ...
  let effect;
  let getter;
  let cleanup;
  let boundCleanup;
  let forceTrigger = false;
  let isMultiSource = false;
  // 当前例子中的source数据源是ref,直接返回source.value,其他场景自行举例debugger尝试
  if (isRef(source)) {
    getter = () => source.value;
    forceTrigger = isShallow(source);
    // 省略其他source类型
  }
  // 如果配置参数中有deep,需要递归traverse处理
  if (cb && deep) {
    const baseGetter = getter;
    const depth = deep === true ? Infinity : deep;
    getter = () => traverse(baseGetter(), depth);
  }
  const scope = getCurrentScope();
  const watchHandle = () => {
    effect.stop();
    if (scope && scope.active) {
      remove(scope.effects, effect);
    }
  };
  // 如果配置参数中有once,执行一次后停止
  if (once && cb) {
    const _cb = cb;
    cb = (...args) => {
      _cb(...args);
      watchHandle();
    };
  }
  let oldValue = isMultiSource
    ? new Array(source.length).fill(INITIAL_WATCHER_VALUE)
    : INITIAL_WATCHER_VALUE;
  // 构建job函数
  const job = () => {
    // ...
  };
  if (augmentJob) {
    augmentJob(job);
  }
  // 这里是重点,实例化`ReactiveEffect`得到`effect`
  effect = new ReactiveEffect(getter);
  effect.scheduler = scheduler ? () => scheduler(job, false) : job;
  boundCleanup = (fn) => onWatcherCleanup(fn, false, effect);
  cleanup = effect.onStop = () => {
    const cleanups = cleanupMap.get(effect);
    if (cleanups) {
      if (call) {
        call(cleanups, 4);
      } else {
        for (const cleanup2 of cleanups) cleanup2();
      }
      cleanupMap.delete(effect);
    }
  };
  {
    effect.onTrack = options.onTrack;
    effect.onTrigger = options.onTrigger;
  }
  if (cb) {
    // 如果参数中有immediate,立即执行一次job函数
    if (immediate) {
      job(true);
    } else {
      // 否则执行effect的run函数,将其作为旧值oldValue
      oldValue = effect.run();
    }
  } else if (scheduler) {
    scheduler(job.bind(null, true), true);
  } else {
    effect.run();
  }
  // 修改watchHandle的pause、resume、stop方法,让其指向effect,使其可以控制effect的暂停、恢复、停止
  watchHandle.pause = effect.pause.bind(effect);
  watchHandle.resume = effect.resume.bind(effect);
  watchHandle.stop = watchHandle;
  return watchHandle;
}

当执行到Vue.watch(count, (newValue, oldValue) => { console.log(newValue, oldValue); })时,会执行侦听器初始化函数,最终执行到effect = new ReactiveEffect(getter)

当前例子中getterref,所以,getter = () => source.value,即getter = () => count.value

这里需要注意oldValue = effect.run();中的run方法,该方法是ReactiveEffect类的实例方法。

ReactiveEffect的实现留个悬念,我们继续看render渲染函数的实现。

2、从render中寻找ReactiveEffect

render视图渲染的逻辑很复杂,可以参考文章: # vue3.5.18 源码:组件树渲染原理

这里主要截取setupRenderEffect函数:

ts 复制代码
const setupRenderEffect = (
  instance,
  initialVNode,
  container,
  anchor,
  parentSuspense,
  isSVG,
  optimized
) => {
  const componentUpdateFn = () => {
    // ...
    // 获取子树subTree
    const subTree = (instance.subTree = renderComponentRoot(instance));
    // ...
    // 渲染子树
    patch(
      null,
      subTree,
      container,
      anchor,
      instance,
      parentSuspense,
      namespace
    );
    // ...
  };
  // 实例化ReactiveEffect实例
  const effect = (instance.effect = new ReactiveEffect(componentUpdateFn));
  // 定义update函数
  const update = (instance.update = effect.run.bind(effect));
  // 省略其他逻辑
  update();
};

通过new ReactiveEffect的方式创建ReactiveEffect实例,并赋值给instance.effect

通过const update = (instance.update = () => effect.run())的方式为instance.update赋值调用effect.run()的函数。

至此,我们可以总结watchrender都用到了ReactiveEffect类,我们继续看ReactiveEffect类的实现。

3、ReactiveEffect

ReactiveEffect的实例化对象就是一个订阅者。渲染过程的调度过程就是由其实例化对象中的run方法进行的,核心代码如下:

ts 复制代码
// ReactiveEffect类
class ReactiveEffect {
  constructor(fn) {
    // 需要执行的函数
    this.fn = fn;
    this.deps = void 0;
    this.depsTail = void 0;
    this.flags = 1 | 4;
    this.next = void 0;

    this.cleanup = void 0;
    this.scheduler = void 0;
    if (activeEffectScope && activeEffectScope.active) {
      activeEffectScope.effects.push(this);
    }
  }
  pause() {
    // 省略逻辑...
  }
  resume() {
    // 省略逻辑...
  }
  notify() {
    // 省略逻辑...
  }
  run() {
    // 设置当前活跃的订阅者为this,即effect
    activeSub = this;
    try {
      return this.fn();
    } finally {
      // 省略逻辑...
    }
  }
  stop() {
    // 省略逻辑...
  }
  trigger() {
    // 省略逻辑...
  }
  runIfDirty() {
    // 省略逻辑...
  }
  get dirty() {
    // 省略逻辑...
  }
}

watch或者render中,每次执行effect.run()时,都会将当前的effect赋值给activeSub,以便于让发布者对订阅者进行收集。

这里需要注意的是,computed既是发布者又是订阅者的双重属性,可以参考文章: # vue3.5.18 源码:既是发布者又是订阅者的 computed

介绍完发布者类Dep和订阅者类ReactiveEffect,我们继续看refcomputedwatchrender如何建立关系。

三、发布订阅者模式-建立关系

发布者Dep收集订阅者的核心方法track如下:

ts 复制代码
// this.dep.track
track(debugInfo) {
  if (!activeSub || !shouldTrack || activeSub === this.computed) {
    return;
  }
  let link = this.activeLink;
  if (link === void 0 || link.sub !== activeSub) {
    // 建立activeSub和dep的关系
    link = this.activeLink = new Link(activeSub, this);
    if (!activeSub.deps) {
      // 订阅者的依赖deps指向link
      activeSub.deps = activeSub.depsTail = link;
    } else {
      // 链表的形式可以管理多个订阅者
      link.prevDep = activeSub.depsTail;
      activeSub.depsTail.nextDep = link;
      activeSub.depsTail = link;
    }
    addSub(link);
  }
  return link;
}
// Link类的实例将包含sub和dep两个属性,分别指向依赖和订阅者
class Link {
  constructor(sub, dep) {
    this.sub = sub;
    this.dep = dep;
    this.version = dep.version;
    // 链表的形式管理dep和sub
    this.nextDep = this.prevDep = this.nextSub = this.prevSub = this.prevActiveLink = void 0;
  }
}
// addSub函数
function addSub(link) {
  link.dep.sc++;
  if (link.sub.flags & 4) {
    if (link.dep.subsHead === void 0) {
      // link.dep.subsHead作为链表头,起初也指向link
      link.dep.subsHead = link;
    }
    // 依赖的订阅者subs也指向link
    link.dep.subs = link;
  }
}

这里需要重点关注的是activeSub.deps = activeSub.depsTail = linklink.dep.subs = link,两者都指向了同一个link对象,该对象包含depsub属性,这样activeSubdep之间建立了关系。实现了你中有我, 我中有你的双向依赖关系。从而数据变化时的找到对应的订阅者,进入触发更新的流程。

所有的发布者和订阅者关系的建立都是以上逻辑,只是触发的时机稍有不同,接下来一一介绍

1、refwatch

在首次执行Vue.watch函数时,会执行到const oldValue = effect.run(),即执行ReactiveEffect实例的run方法。

run方法中,首先执行activeSub = this将当前effect定义为当前活跃状态的订阅者。

接着执行this.fn(),即执行getter函数。在当前实例中getter = () => source.value,触发count.valueget方法,最终执行到track函数进行refwatch之间关系的建立。

2、refrender

在首次执行render函数中的const subTree = (instance.subTree = renderComponentRoot(instance))时,template模版中包含{{count}},因此会触发count.valueget方法,最终执行到track函数进行refrender之间关系的建立。

3、computedrender

在首次执行render函数中的const subTree = (instance.subTree = renderComponentRoot(instance))时,template模版中包含{{computedCount}},因此会触发computedCount.valueget方法,最终执行到this.dep.track函数进行computedrender之间关系的建立。

4、refcomputed

在执行完this.dep.track后,又执行了refreshComputed(this)

ts 复制代码
function refreshComputed(computed) {
  // 省略其他逻辑
  const dep = computed.dep;
  const prevSub = activeSub;
  const prevShouldTrack = shouldTrack;
  // 这里将activeSub赋值为computed,以方便建立computed和count之间的关系
  activeSub = computed;
  shouldTrack = true;
  try {
    prepareDeps(computed);
    // 这里执行computed.fn,即count.value++
    const value = computed.fn(computed._value);
    if (dep.version === 0 || hasChanged(value, computed._value)) {
      computed.flags |= 128;
      computed._value = value;
      dep.version++;
    }
  } catch (err) {
    dep.version++;
    throw err;
  } finally {
    // 这里将activeSub赋值为prevSub,恢复activeSub的值
    activeSub = prevSub;
    shouldTrack = prevShouldTrack;
    cleanupDeps(computed);
    computed.flags &= -3;
  }
}

以上代码中通过activeSub = computed的方式,将computed作为订阅者,再通过const value = computed.fn(computed._value)的方式执行到代码count.value * 2。近而触发countget函数,即this.dep.track({ target: this, type: "get", key: "value", });

这样就建立了refcomputed之间的关系。

5、computedwatch

在首次执行Vue.watch函数时,会执行到const oldValue = effect.run(),即执行ReactiveEffect实例的run方法。

run方法中,首先执行activeSub = this将当前effect定义为当前活跃状态的订阅者。

接着执行this.fn(),即执行getter函数。在当前实例中getter = () => source.value,触发computedCount.valueget方法,最终执行到track函数进行computedwatch之间关系的建立。

至此,就完成了refcomputedwatchrender之间的订阅者关系建立。虽然时机各不相同,但是,核心逻辑都是一样的,即访问refcomputed的值时,会访问到其get函数,最终执行到track函数建立订阅者之间的双向依赖关系。

四、发布订阅者模式-发布者通知订阅者

当执行count.value++操作时,会触发countset函数,进而执行到this.dep.trigger函数,派发更新的核心代码如下:

依赖Dep触发逻辑:

ts 复制代码
// Dep的trigger函数
trigger(debugInfo) {
  this.version++;
  globalVersion++;
  this.notify(debugInfo);
}
// Dep的notify函数
notify(debugInfo) {
  startBatch();
  try {
    // 通过链表的形式,执行所有的订阅者
    for (let link = this.subs; link; link = link.prevSub) {
      // 执行订阅者link.sub的notify
      if (link.sub.notify()) {
        ;
        link.sub.dep.notify();
      }
    }
  } finally {
    endBatch();
  }
}

订阅者Sub设置当前batchedsub逻辑:

ts 复制代码
// sub的notify函数
notify() {
  if (this.flags & 2 && !(this.flags & 32)) {
    return;
  }
  if (!(this.flags & 8)) {
    batch(this);
  }
}
// batch函数
function batch(sub, isComputed = false) {
  sub.flags |= 8;
  if (isComputed) {
    sub.next = batchedComputed;
    batchedComputed = sub;
    return;
  }
  sub.next = batchedSub;
  // batchedSub指向第一个sub
  batchedSub = sub;
}

当以上逻辑结束时,继续回到DependBatch方法:

ts 复制代码
// dep的endBatch方法
function endBatch() {
  if (--batchDepth > 0) {
    return;
  }
  // 省略计算属性相关的逻辑...
  while (batchedSub) {
    let e = batchedSub;
    batchedSub = void 0;
    while (e) {
      const next = e.next;
      e.next = void 0;
      e.flags &= -9;
      if (e.flags & 1) {
        try {
          // 执行e(batchedSub)的trigger()方法
          e.trigger();
        } catch (err) {
          if (!error) error = err;
        }
      }
      e = next;
    }
  }
  if (error) throw error;
}
// sub的trigger()方法
trigger() {
  if (this.flags & 64) {
    pausedQueueEffects.add(this);
  } else if (this.scheduler) {
    // 即effect.scheduler = () => queueJob(job);
    this.scheduler();
  } else {
    this.runIfDirty();
  }
}

再看 queueJob 的核心逻辑

ts 复制代码
function queueJob(job) {
  if (!(job.flags & 1)) {
    const jobId = getId(job);
    const lastJob = queue[queue.length - 1];
    if (!lastJob || (!(job.flags & 2) && jobId >= getId(lastJob))) {
      // 将当前任务插入到数组尾部
      queue.push(job);
    } else {
      // 根据jobId,将其移动到合适的位置
      queue.splice(findInsertionIndex(jobId), 0, job);
    }
    job.flags |= 1;
    queueFlush();
  }
}
// queueFlush
function queueFlush() {
  if (!currentFlushPromise) {
    // flushJobs是异步任务,得等下个异步队列才执行
    currentFlushPromise = resolvedPromise.then(flushJobs);
  }
}

下一个异步队列,执行的任务:

ts 复制代码
function flushJobs(seen) {
  {
    seen = seen || /* @__PURE__ */ new Map();
  }
  const check = (job) => checkRecursiveUpdates(seen, job);
  try {
    for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
      const job = queue[flushIndex];
      if (job && !(job.flags & 8)) {
        if (check(job)) {
          continue;
        }
        if (job.flags & 4) {
          job.flags &= ~1;
        }
        // 在错误处理函数中执行job
        callWithErrorHandling(job, job.i, job.i ? 15 : 14);
        if (!(job.flags & 4)) {
          job.flags &= ~1;
        }
      }
    }
  } finally {
    for (; flushIndex < queue.length; flushIndex++) {
      const job = queue[flushIndex];
      if (job) {
        job.flags &= -2;
      }
    }
    flushIndex = -1;
    queue.length = 0;
    flushPostFlushCbs(seen);
    currentFlushPromise = null;
    if (queue.length || pendingPostFlushCbs.length) {
      flushJobs(seen);
    }
  }
}
// 错误处理函数
function callWithErrorHandling(fn, instance, type, args) {
    try {
      return args ? fn(...args) : fn();
    } catch (err) {
      handleError(err, instance, type);
    }
  }
// 因为job = instance.job = effect.runIfDirty.bind(effect);,所以,fn就是runIfDirty函数
runIfDirty() {
  if (isDirty(this)) {
    // 这里就是最终的渲染逻辑
    this.run();
  }
}

以上,就是从数据count变化,到订阅者收到消息后做出反应的全过程。


这是一个vue3.5.18源码精读系列,欢迎大家持续关注,一起学习,共同进步。

相关推荐
Xiecj7 小时前
使用Vue3实现鼠标跟随效果
前端
A洛7 小时前
Claude Code项目流飞升!AI自动化测试迎来新纪元:Playwright MCP复用Chrome登录态
前端·人工智能·chrome
黑狼传说7 小时前
从一行 var a = 1 开始,深入理解 V8 引擎的心脏
前端·javascript·v8
原生高钙7 小时前
var, let 和 const
前端·javascript·面试
huabuyu7 小时前
Taro微信小程序高性能无限下拉列表实现
前端
DevRen8 小时前
实现Google原生PIN码锁屏密码效果
android·前端·kotlin
ZSQA8 小时前
mac安装Homebrew解决网络问题
前端
烽学长8 小时前
(附源码)基于Vue的教师档案管理系统的设计与实现
前端·javascript·vue.js
前端一课8 小时前
前端监控 SDK,支持页面访问、性能监控、错误追踪、用户行为和网络请求监控
前端