首先我们罗列一下响应式相关的核心功能:
ref
,数据变化时,对应的computed
、watch
、render
都会更新computed
,数据变化时,对应的watch
和render
会更新watch
,会根据ref
的数据变化来执行副作用
函数render
,会根据ref
或computed
的数据变化来重新渲染视图
看完本文,会梳理明白以上核心功能底层是如何实现的,以及它们之间的关系:
Dep
核心类,RefImpl
和ComputedRefImpl
的实例均依赖于Dep
类进行依赖管理ReactiveEffect
核心类,watch
和render
的底层逻辑中,订阅者功能由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
类定义了取值和赋值两个方法:get
和set
, 在访问它时,会执行get
函数,进而执行this.dep.track
收集依赖。在数据变化时,会执行set
函数,进而执行this.dep.trigger
派发更新。
我们发现,RefImpl
的get
和set
方法,都依赖了Dep
类的实例化对象this.dep
,所以RefImpl
和Dep
类是紧密相关的。
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
类定义了取值和赋值两个方法:get
和set
, 在访问到计算属性时,会执行到get
函数,进而执行this.dep.track
收集依赖。
也可通过给computed
传递get
和set
的方式,自定义set
函数。此时可以直接修改计算属的值,进而执行this.setter(newValue)
函数(这里不是主线逻辑,暂不展开)。
我们发现,ComputedRefImpl
的get
方法,依赖了Dep
类的实例化对象this.dep
,所以ComputedRefImpl
和Dep
类也是紧密相关的。
接下来,是时候看看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
类的实现,它定义了track
、trigger
和notify
三个方法,分别对应依赖收集、派发更新和通知更新,这个我们在发布者和订阅者构建关系的部分详细介绍。
到这里我们已经了解了RefImpl
类、ComputedRefImpl
类和Dep
类之间的依赖关系。
RefImpl
类和ComputedRefImpl
类对于订阅者的收集都是通过Dep
的track
方法来实现的。
二、发布订阅者模式-订阅者类
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)
。
当前例子中getter
是ref
,所以,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()
的函数。
至此,我们可以总结watch
和render
都用到了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
,我们继续看ref
、computed
、watch
和render
如何建立关系。
三、发布订阅者模式-建立关系
发布者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 = link
和link.dep.subs = link
,两者都指向了同一个link
对象,该对象包含dep
和sub
属性,这样activeSub
和dep
之间建立了关系。实现了你中有我, 我中有你
的双向依赖关系。从而数据变化时的找到对应的订阅者,进入触发更新的流程。
所有的发布者和订阅者关系的建立都是以上逻辑,只是触发的时机稍有不同,接下来一一介绍
1、ref
和watch
在首次执行Vue.watch
函数时,会执行到const oldValue = effect.run()
,即执行ReactiveEffect
实例的run
方法。
在run
方法中,首先执行activeSub = this
将当前effect
定义为当前活跃状态的订阅者。
接着执行this.fn()
,即执行getter
函数。在当前实例中getter = () => source.value
,触发count.value
的get
方法,最终执行到track
函数进行ref
和watch
之间关系的建立。
2、ref
和render
在首次执行render
函数中的const subTree = (instance.subTree = renderComponentRoot(instance))
时,template
模版中包含{{count}}
,因此会触发count.value
的get
方法,最终执行到track
函数进行ref
和render
之间关系的建立。
3、computed
和render
在首次执行render
函数中的const subTree = (instance.subTree = renderComponentRoot(instance))
时,template
模版中包含{{computedCount}}
,因此会触发computedCount.value
的get
方法,最终执行到this.dep.track
函数进行computed
和render
之间关系的建立。
4、ref
和computed
在执行完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
。近而触发count
的get
函数,即this.dep.track({ target: this, type: "get", key: "value", });
。
这样就建立了ref
和computed
之间的关系。
5、computed
和watch
在首次执行Vue.watch
函数时,会执行到const oldValue = effect.run()
,即执行ReactiveEffect
实例的run
方法。
在run
方法中,首先执行activeSub = this
将当前effect
定义为当前活跃状态的订阅者。
接着执行this.fn()
,即执行getter
函数。在当前实例中getter = () => source.value
,触发computedCount.value
的get
方法,最终执行到track
函数进行computed
和watch
之间关系的建立。
至此,就完成了ref
、computed
、watch
和render
之间的订阅者关系建立。虽然时机各不相同,但是,核心逻辑都是一样的,即访问ref
和computed
的值时,会访问到其get
函数,最终执行到track
函数建立订阅者之间的双向依赖关系。
四、发布订阅者模式-发布者通知订阅者
当执行count.value++
操作时,会触发count
的set
函数,进而执行到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;
}
当以上逻辑结束时,继续回到Dep
的endBatch
方法:
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
源码精读系列,欢迎大家持续关注,一起学习,共同进步。