与 vue 相关的笔记,导师说方法比编码更加重要,那就从分析vuejs/core
开始吧。
-
计划:分析 reactivity 包的内容。
-
github 上有个关于 vue3 响应式原理的电子书,不过最后 commit 时间是 4 年前,现在 vue3 代码组织已经与其不太一样了(都重构几轮了)。而且内容和组件渲染那块耦合在一起了感觉。网上有个关于 vue3.5 响应式原理重构的文章,写的很好,本文对此有参考,但是那个文章在 track 那块讲的感觉和源码还是不太一致。
Reactivity 实现:
入口:
export function reactive<T extends object>(target: T): Reactive<T>;
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (isReadonly(target)) {
return target;
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
);
}
注意这里的 isReadonly 的实现是如下:
/**
* Checks whether the passed value is a readonly object. The properties of a
* readonly object can change, but they can't be assigned directly via the
* passed object.
*
* The proxies created by {@link readonly} and {@link shallowReadonly} are
* both considered readonly, as is a computed ref without a set function.
*
* @param value - The value to check.
* @see {@link https://vuejs.org/api/reactivity-utilities.html#isreadonly}
*/
export function isReadonly(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_READONLY]);
}
这里是判断由readonly
&shallowreadonly
方法创建的 proxy 或是未定义 set 属性的 ref。不是如 Object.freeze 等类似方法冻结的对象。
对于createReactiveObject
分析:
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
if (!isObject(target)) {
if (__DEV__) {
warn(
`value cannot be made ${isReadonly ? "readonly" : "reactive"}: ${String(
target
)}`
);
}
return target;
}
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
if (
target[ReactiveFlags.RAW] &&
// 考虑readonly(reactive(object))
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target;
}
// only specific value types can be observed.
const targetType = getTargetType(target);
if (targetType === TargetType.INVALID) {
return target;
}
// target already has corresponding Proxy
const existingProxy = proxyMap.get(target);
if (existingProxy) {
return existingProxy;
}
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
);
proxyMap.set(target, proxy);
return proxy;
}
- 非对象和已代理对象不进行处理,除非即将对于一个已代理对象创建只读代理(前面提到的 readonly 方法)
getTargetType 实现如下:
function getTargetType(value: Target) {
return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
? TargetType.INVALID
: targetTypeMap(toRawType(value));
}
function targetTypeMap(rawType: string) {
switch (rawType) {
case "Object":
case "Array":
return TargetType.COMMON;
case "Map":
case "Set":
case "WeakMap":
case "WeakSet":
return TargetType.COLLECTION;
default:
return TargetType.INVALID;
}
}
通过这里我们可以发现,如果是被Object.preventExtensions
处理的对象也是不合法的,无法正确跟踪,会返回原对象。具体原因与fix(reactive): use isExtensible instead of isFrozen #1753这个相关,和 seal 处理过的对象不合法类似,vue 需要对对象赋值一些额外的属性(参见那些 enumeration,理论上是不是用 weakmap 可以优化这一问题?)。以下是测试:
import { reactive } from "@vue/reactivity";
const uExObj = Object.preventExtensions({
name: "John",
});
const uExReactive = reactive(uExObj);
uExReactive.name = "Doe";
console.log(uExReactive.name); // "Doe"
console.log(uExReactive === uExObj); // true
继续,这里 toRawType 和 Object.prototype.toString 类似,截取其中[8, -1]字符串内容。
下面分析 baseHandler
class BaseReactiveHandler implements ProxyHandler<Target> {
constructor(
protected readonly _isReadonly = false,
protected readonly _isShallow = false
) {}
get(target: Target, key: string | symbol, receiver: object): any {
if (key === ReactiveFlags.SKIP) return target[ReactiveFlags.SKIP];
const isReadonly = this._isReadonly,
isShallow = this._isShallow;
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly;
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly;
} else if (key === ReactiveFlags.IS_SHALLOW) {
return isShallow;
} else if (key === ReactiveFlags.RAW) {
if (
// 此处readonlyMap and reactiveMap都是reactivity.ts中的内容,target => proxy.用处就是toRaw函数
receiver ===
(isReadonly
? isShallow
? shallowReadonlyMap
: readonlyMap
: isShallow
? shallowReactiveMap
: reactiveMap
).get(target) ||
// receiver is not the reactive proxy, but has the same prototype
// this means the receiver is a user proxy of the reactive proxy
Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)
) {
return target;
}
// early return undefined
return;
}
const targetIsArray = isArray(target);
if (!isReadonly) {
let fn: Function | undefined;
// 这里是对于数组的一些操作进行自定义的实现。比如push就noTracking之类的。
if (targetIsArray && (fn = arrayInstrumentations[key])) {
return fn;
}
if (key === "hasOwnProperty") {
return hasOwnProperty;
}
}
const res = Reflect.get(
target,
key,
// if this is a proxy wrapping a ref, return methods using the raw ref
// as receiver so that we don't have to call `toRaw` on the ref in all
// its class methods
isRef(target) ? target : receiver
);
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res;
}
// 执行依赖追踪
if (!isReadonly) {
track(target, TrackOpTypes.GET, key);
}
if (isShallow) {
return res;
}
// 要是用js很难不出错😂。
if (isRef(res)) {
// ref unwrapping - skip unwrap for Array + integer key.
// 这里的isIntergerKey是无符号的。
return targetIsArray && isIntegerKey(key) ? res : res.value;
}
if (isObject(res)) {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return isReadonly ? readonly(res) : reactive(res);
}
return res;
}
}
这里可以发现,如reactive({test: ref(1)}).test
是可以直接读到值的,当然如上图,shallowRef 就没法自动解包。至于为什么要这样设计个人不是很明白,似乎是有些过度设计了吧,和 shallowRef 一样的返回应该更符合直觉吧,不过大佬的项目肯定有大佬的道理。这个特性在 vue3 的文档里也有提及,不过关于 shallowRef 不能自动包的事情并没有被提及。
值得注意的还有这一段:
if (isObject(res)) {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return isReadonly ? readonly(res) : reactive(res);
}
Vue 的深层监听是在 get 中实现的,也就是说未涉及到的对象并不会被深层地转换为响应式对象,这个设计很巧妙。而且配合前面我们看到的 proxyMap,也实现了对于同一对象的响应式对象不会被重复创建。
接下来是 MutableReactiveHandler,对于 Object 和 Array 的实现。
class MutableReactiveHandler extends BaseReactiveHandler {
constructor(isShallow = false) {
super(false, isShallow);
}
set(
target: Record<string | symbol, unknown>,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
let oldValue = target[key];
if (!this._isShallow) {
const isOldValueReadonly = isReadonly(oldValue);
if (!isShallow(value) && !isReadonly(value)) {
oldValue = toRaw(oldValue);
value = toRaw(value);
}
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
if (isOldValueReadonly) {
return false;
} else {
oldValue.value = value;
return true;
}
}
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
}
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key);
const result = Reflect.set(
target,
key,
value,
isRef(target) ? target : receiver
);
// don't trigger if target is something up in the prototype chain of original
if (target === toRaw(receiver)) {
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value);
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue);
}
}
return result;
}
deleteProperty(
target: Record<string | symbol, unknown>,
key: string | symbol
): boolean {
const hadKey = hasOwn(target, key);
const oldValue = target[key];
const result = Reflect.deleteProperty(target, key);
if (result && hadKey) {
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue);
}
return result;
}
has(target: Record<string | symbol, unknown>, key: string | symbol): boolean {
const result = Reflect.has(target, key);
if (!isSymbol(key) || !builtInSymbols.has(key)) {
track(target, TrackOpTypes.HAS, key);
}
return result;
}
ownKeys(target: Record<string | symbol, unknown>): (string | symbol)[] {
track(
target,
TrackOpTypes.ITERATE,
isArray(target) ? "length" : ITERATE_KEY
);
return Reflect.ownKeys(target);
}
}
这里先关注 setHandler:
let oldValue = target[key];
if (!this._isShallow) {
const isOldValueReadonly = isReadonly(oldValue);
if (!isShallow(value) && !isReadonly(value)) {
oldValue = toRaw(oldValue);
value = toRaw(value);
}
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
if (isOldValueReadonly) {
return false;
} else {
oldValue.value = value;
return true;
}
}
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
}
这一部分与之前的部分结合,就会诞生以下效果:
const testRef = ref(1);
const testReactive = reactive({
ref: testRef,
});
const testShallowReactive = shallowReactive({
ref: testRef,
});
testReactive.ref = 2;
console.log(testReactive.ref, testRef.value); // 2, 2
testShallowReactive.ref.value = 3;
console.log(testShallowReactive.ref.value, testRef.value); // 3, 3
好在正常情况下 reactive 中嵌套 ref 的情况并不多见,并且 ts 也有良好的类型提醒。
然后是 ref 相关,实现相对不会复杂,这里贴出关键代码:
/**
* @internal
*/
class RefImpl<T = any> {
_value: T;
private _rawValue: T;
dep: Dep = new Dep();
public readonly [ReactiveFlags.IS_REF] = true;
public readonly [ReactiveFlags.IS_SHALLOW]: boolean = false;
constructor(value: T, isShallow: boolean) {
this._rawValue = isShallow ? value : toRaw(value);
this._value = isShallow ? value : toReactive(value);
this[ReactiveFlags.IS_SHALLOW] = isShallow;
}
get value() {
if (__DEV__) {
this.dep.track({
target: this,
type: TrackOpTypes.GET,
key: "value",
});
} else {
this.dep.track();
}
return this._value;
}
set value(newValue) {
const oldValue = this._rawValue;
const useDirectValue =
this[ReactiveFlags.IS_SHALLOW] ||
isShallow(newValue) ||
isReadonly(newValue);
newValue = useDirectValue ? newValue : toRaw(newValue);
if (hasChanged(newValue, oldValue)) {
this._rawValue = newValue;
this._value = useDirectValue ? newValue : toReactive(newValue);
if (__DEV__) {
this.dep.trigger({
target: this,
type: TriggerOpTypes.SET,
key: "value",
newValue,
oldValue,
});
} else {
this.dep.trigger();
}
}
}
}
到这里,其实对于 vue3 的依赖收集的过程就比较明了了。接着看 track 和 trigger 的实现,也就是依赖收集的具体过程。 vue3.5 对响应式相关内容见进行了重构,这里以最新的 3.5.17 版本为基准。
vue3.5 的依赖收集过程的实现是通过一个二维双向链表实现的。同时还有一个版本计数用于控制依赖的订阅是否被触发。
以下涉及的三个基本对象的意义: Dep: 依赖,如 reactive 对象这样的概念,基本属性如下:
version = 0
/**
* Link between this dep and the current active effect
*/
activeLink?: Link = undefined
/**
* Doubly linked list representing the subscribing effects (tail)
*/
subs?: Link = undefined
/**
* Doubly linked list representing the subscribing effects (head)
* DEV only, for invoking onTrigger hooks in correct order
*/
subsHead?: Link
/**
* For object property deps cleanup
*/
map?: KeyToDepMap = undefined
key?: unknown = undefined
/**
* Subscriber counter
*/
sc: number = 0
Sub: 订阅,如 watch,类型是一个接口,具体如下:
/**
* Subscriber is a type that tracks (or subscribes to) a list of deps.
*/
export interface Subscriber extends DebuggerOptions {
/**
* Head of the doubly linked list representing the deps
* @internal
*/
deps?: Link;
/**
* Tail of the same list
* @internal
*/
depsTail?: Link;
/**
* @internal
*/
flags: EffectFlags;
/**
* @internal
*/
next?: Subscriber;
/**
* returning `true` indicates it's a computed that needs to call notify
* on its dep too
* @internal
*/
notify(): true | void;
}
需要注意的是这里的 deps 和 depsLinks 的属性的类型是 Link 不是 Dep Link:这里指双向链表的一个结点。实现如下:
export class Link {
/**
* - Before each effect run, all previous dep links' version are reset to -1
* - During the run, a link's version is synced with the source dep on access
* - After the run, links with version -1 (that were never used) are cleaned
* up
*/
version: number;
/**
* Pointers for doubly-linked lists
*/
nextDep?: Link;
prevDep?: Link;
nextSub?: Link;
prevSub?: Link;
prevActiveLink?: Link;
constructor(public sub: Subscriber, public dep: Dep) {
this.version = dep.version;
this.nextDep =
this.prevDep =
this.nextSub =
this.prevSub =
this.prevActiveLink =
undefined;
}
}
注意 computed 同时属于 Dep 和 Sub,后面会涉及与其相关的特殊优化。
这里先分析 Dep 对象的 track 方法的实现:
track(debugInfo?: DebuggerEventExtraInfo): Link | undefined {
if (!activeSub || !shouldTrack || activeSub === this.computed) {
return
}
let link = this.activeLink
if (link === undefined || link.sub !== activeSub) {
link = this.activeLink = new Link(activeSub, this)
// add the link to the activeEffect as a dep (as tail)
if (!activeSub.deps) {
activeSub.deps = activeSub.depsTail = link
} else {
link.prevDep = activeSub.depsTail
activeSub.depsTail!.nextDep = link
activeSub.depsTail = link
}
addSub(link)
} else if (link.version === -1) {
// reused from last run - already a sub, just sync version
link.version = this.version
// If this dep has a next, it means it's not at the tail - move it to the
// tail. This ensures the effect's dep list is in the order they are
// accessed during evaluation.
if (link.nextDep) {
const next = link.nextDep
next.prevDep = link.prevDep
if (link.prevDep) {
link.prevDep.nextDep = next
}
link.prevDep = activeSub.depsTail
link.nextDep = undefined
activeSub.depsTail!.nextDep = link
activeSub.depsTail = link
// this was the head - point to the new head
if (activeSub.deps === link) {
activeSub.deps = next
}
}
}
if (__DEV__ && activeSub.onTrack) {
activeSub.onTrack(
extend(
{
effect: activeSub,
},
debugInfo,
),
)
}
return link
}
// 涉及到的函数
function addSub(link: Link) {
link.dep.sc++
if (link.sub.flags & EffectFlags.TRACKING) {
const computed = link.dep.computed
// computed getting its first subscriber
// enable tracking + lazily subscribe to all its deps
if (computed && !link.dep.subs) {
computed.flags |= EffectFlags.TRACKING | EffectFlags.DIRTY
for (let l = computed.deps; l; l = l.nextDep) {
addSub(l)
}
}
const currentTail = link.dep.subs
if (currentTail !== link) {
link.prevSub = currentTail
if (currentTail) currentTail.nextSub = link
}
if (__DEV__ && link.dep.subsHead === undefined) {
link.dep.subsHead = link
}
link.dep.subs = link
}
}
这里面 activeSub 是一个全局变量,表示当前正在处理的 Subscriber。
先判断 this.activeLink(Link between this dep and the current active effect),执行以下两种逻辑:
-
若 undefined(尚未建立链接)或是 this.activeLink.sub !== activeSub(链接不数期望的),则创建新的 Link 结点,之后将新结点链接到 activeSub.deps 末尾。然后执行 addSub 函数。
对于 addSub 函数,执行时先将其指向的 dep 的 sc++(Subscriber counter),然后若 link.sub.flags 包含 EffectFlags.TRACKING 则执行后续依赖追踪逻辑:若其有 computed 属性,就执行特殊优化,否则将当前结点链接至当前 dep 的链表的尾部。对于计算属性的特殊优化内容如下:
先判断是否有 link.dep.subs 若空则跳过 computed 的特殊处理,以下是我的理解:这里的设计实际上是在第一次执行 addSub 的时候不进行 if 内的逻辑,因为后面的逻辑就会将 link.dep.subs 进行赋值(上文的链接到末尾部分)。if 块内的内容逻辑是将 computed 标记为 EffectFlags.TRACKING | EffectFlags.DIRTY,其中 DIRTY 的含义是需要重新进行计算。然后从 computed 的 deps 结尾向前依次初始化。
总结一下就是当 dep 和 sub 的链接未创建时创建一个结点进行链接(computed 懒链接)。
-
若 link.version === -1,则执行下面逻辑(version 后面再说): 同步 link.version = this.version,然后若 link 存在 nextDep(If this dep has a next, it means it’s not at the tail),将其移动至尾部,原因注释中写了。
比如在 refImpl 的 get value()触发时就会触发 this.dep.track(), 将 refImpl 和当前的 activeSub 链接起来。
trigger 的内容如下:
trigger(debugInfo?: DebuggerEventExtraInfo): void {
this.version++
globalVersion++
this.notify(debugInfo)
}
notify(debugInfo?: DebuggerEventExtraInfo): void {
startBatch()
try {
if (__DEV__) {
// subs are notified and batched in reverse-order and then invoked in
// original order at the end of the batch, but onTrigger hooks should
// be invoked in original order here.
for (let head = this.subsHead; head; head = head.nextSub) {
if (head.sub.onTrigger && !(head.sub.flags & EffectFlags.NOTIFIED)) {
head.sub.onTrigger(
extend(
{
effect: head.sub,
},
debugInfo,
),
)
}
}
}
for (let link = this.subs; link; link = link.prevSub) {
if (link.sub.notify()) {
// if notify() returns `true`, this is a computed. Also call notify
// on its dep - it's called here instead of inside computed's notify
// in order to reduce call stack depth.
;(link.sub as ComputedRefImpl).dep.notify()
}
}
} finally {
endBatch()
}
}
对于 computed,这里的 notify 内容如下:
/**
* @internal
*/
notify(): true | void {
this.flags |= EffectFlags.DIRTY
if (
// 确保一个更新周期内只被通知一次
!(this.flags & EffectFlags.NOTIFIED) &&
// avoid infinite self recursion
activeSub !== this
) {
batch(this, true)
return true
} else if (__DEV__) {
// TODO warn
}
}
这里 notify 触发的 sub.notify 实现如下:
export class ReactiveEffect<T = any>
implements Subscriber, ReactiveEffectOptions
{
...
notify(): void {
if (
this.flags & EffectFlags.RUNNING &&
!(this.flags & EffectFlags.ALLOW_RECURSE)
) {
return
}
if (!(this.flags & EffectFlags.NOTIFIED)) {
batch(this)
}
}
}
实际就是将很多个副作用放在一个 bench 中执行,说是提升性能表现。bench 中实际相关触发副作用的代码实现如下:
export function endBatch(): void {
if (--batchDepth > 0) {
return;
}
if (batchedComputed) {
let e: Subscriber | undefined = batchedComputed;
batchedComputed = undefined;
while (e) {
const next: Subscriber | undefined = e.next;
e.next = undefined;
e.flags &= ~EffectFlags.NOTIFIED;
e = next;
}
}
let error: unknown;
while (batchedSub) {
let e: Subscriber | undefined = batchedSub;
batchedSub = undefined;
while (e) {
const next: Subscriber | undefined = e.next;
e.next = undefined;
e.flags &= ~EffectFlags.NOTIFIED;
if (e.flags & EffectFlags.ACTIVE) {
try {
// ACTIVE flag is effect-only
(e as ReactiveEffect).trigger();
} catch (err) {
if (!error) error = err;
}
}
e = next;
}
}
if (error) throw error;
}
注意到实际触发的是(e as ReactiveEffect).trigger(),其代码实现如下:
trigger(): void {
if (this.flags & EffectFlags.PAUSED) {
// pausedQueueEffects是weakSet
pausedQueueEffects.add(this)
} else if (this.scheduler) {
// 这里调度器作用就是延迟触发,修改触发时机之类的。
this.scheduler()
} else {
this.runIfDirty()
}
}
runIfDirty 与版本计数相关。
要理解 version 相关的内容就需要了解版本计数原理:
-
Dep 构造时会将 version 设置为 0,构造 Link 时会将 Link 的值设置为与 dep.version 相同。每次 trigger 触发时(比如 ref{set value()}) version 和 globalVersion 自增(globalVersion 是一个全局变量)。
-
由前文可知 notify 最终会走到 runIfDirty,相关的代码实现如下:
/**
* @internal
*/
runIfDirty(): void {
if (isDirty(this)) {
this.run()
}
}
function isDirty(sub: Subscriber): boolean {
for (let link = sub.deps; link; link = link.nextDep) {
if (
link.dep.version !== link.version ||
(link.dep.computed &&
(refreshComputed(link.dep.computed) ||
link.dep.version !== link.version))
) {
return true
}
}
// @ts-expect-error only for backwards compatibility where libs manually set
// this flag - e.g. Pinia's testing module
if (sub._dirty) {
return true
}
return false
}
分析 isDirty 里面的代码:link.dep.version !== link.version 在对于普通响应式对象时必定为 true 了(trigger 那里 dep.version++导致的 version 不相同)。对于 computed 对象由于其依赖更新不会触发其 version 自增故前一个判断不满足,需要 refreshCoputed 后进行判断。
/**
* Returning false indicates the refresh failed
* @internal
*/
export function refreshComputed(computed: ComputedRefImpl): undefined {
if (
computed.flags & EffectFlags.TRACKING &&
!(computed.flags & EffectFlags.DIRTY)
) {
return;
}
computed.flags &= ~EffectFlags.DIRTY;
// Global version fast path when no reactive changes has happened since
// last refresh.
if (computed.globalVersion === globalVersion) {
return;
}
computed.globalVersion = globalVersion;
// In SSR there will be no render effect, so the computed has no subscriber
// and therefore tracks no deps, thus we cannot rely on the dirty check.
// Instead, computed always re-evaluate and relies on the globalVersion
// fast path above for caching.
// #12337 if computed has no deps (does not rely on any reactive data) and evaluated,
// there is no need to re-evaluate.
if (
!computed.isSSR &&
computed.flags & EffectFlags.EVALUATED &&
((!computed.deps && !(computed as any)._dirty) || !isDirty(computed))
) {
return;
}
// 主要内容从这里开始
computed.flags |= EffectFlags.RUNNING;
// 准备上下文环境
const dep = computed.dep;
const prevSub = activeSub;
const prevShouldTrack = shouldTrack;
activeSub = computed;
shouldTrack = true;
try {
prepareDeps(computed);
const value = computed.fn(computed._value);
// hasChanged eqs !Object.is(value, oldValue)
if (dep.version === 0 || hasChanged(value, computed._value)) {
computed.flags |= EffectFlags.EVALUATED;
computed._value = value;
dep.version++;
}
} catch (err) {
dep.version++;
throw err;
} finally {
// 恢复上下文环境
activeSub = prevSub;
shouldTrack = prevShouldTrack;
cleanupDeps(computed);
computed.flags &= ~EffectFlags.RUNNING;
}
}
function prepareDeps(sub: Subscriber) {
// Prepare deps for tracking, starting from the head
for (let link = sub.deps; link; link = link.nextDep) {
// set all previous deps' (if any) version to -1 so that we can track
// which ones are unused after the run
link.version = -1;
// store previous active sub if link was being used in another context
link.prevActiveLink = link.dep.activeLink;
link.dep.activeLink = link;
}
}
function cleanupDeps(sub: Subscriber) {
// Cleanup unsued deps
let head;
let tail = sub.depsTail;
let link = tail;
while (link) {
const prev = link.prevDep;
if (link.version === -1) {
if (link === tail) tail = prev;
// unused - remove it from the dep's subscribing effect list
removeSub(link);
// also remove it from this effect's dep list
removeDep(link);
} else {
// The new head is the last node seen which wasn't removed
// from the doubly-linked list
head = link;
}
// restore previous active link if any
link.dep.activeLink = link.prevActiveLink;
link.prevActiveLink = undefined;
link = prev;
}
// set the new head & tail
sub.deps = head;
sub.depsTail = tail;
}
这里就比较浅显易懂了,准备上下文,将 sub 维度对应的链的 version 设为-1,执行计算,涉及到的依赖根据上文 version 会和 computed 同步,剩下的未涉及的链表结点通过 cleanupDeps 清理。注意这里如果 value 变了会让 computed.dep.version++,在上文中的 isDirty 中就会检查出来时 Dirty 的,然后就会执行相应的逻辑。
接上上上上文,isDirty 判断为 true 后执行 this.run,具体代码如下:
run(): T {
if (!(this.flags & EffectFlags.ACTIVE)) {
return this.fn()
}
this.flags |= EffectFlags.RUNNING
cleanupEffect(this)
prepareDeps(this)
const prevEffect = activeSub
const prevShouldTrack = shouldTrack
activeSub = this
shouldTrack = true
try {
return this.fn()
} finally {
if (__DEV__ && activeSub !== this) {
warn(
'Active effect was not restored correctly - ' +
'this is likely a Vue internal bug.',
)
}
cleanupDeps(this)
activeSub = prevEffect
shouldTrack = prevShouldTrack
this.flags &= ~EffectFlags.RUNNING
}
}
OK,到此为止吧