Vue 3 的响应式系统基于 Proxy 进行了彻底重构,相比 Vue 2 的 Object.defineProperty 方案,它能够拦截更多操作(如属性的添加/删除、数组索引和 length 修改、Map/Set 等方法调用),并且采用 懒递归 和 精细化依赖管理,性能与可维护性都大幅提升。
副作用
概念
在 Vue 3 的响应式系统里,“副作用”(Effect)是一个核心概念。简单来说,副作用是指那些依赖于响应式数据,并且当这些数据变化时需要重新执行的函数。
- 组件的渲染函数(当数据变化时重新生成虚拟 DOM)
watch或watchEffect的回调(当被监听的数据变化时执行某些逻辑)computed的 getter(当其依赖变化时重新计算值)
这些函数在依赖的数据变化时会被“触发”重新运行,从而产生一些“额外的影响”(比如更新视图、发起请求、操作 DOM 等),因此被称为副作用。
副作用有哪些特点?
- 依赖追踪
副作用函数在执行期间会访问响应式数据(如ref、reactive对象的属性)。Vue 的响应式系统会记录这个副作用与这些数据的对应关系。 - 自动重新执行
当副作用所依赖的数据发生变化时,Vue 会自动将该副作用标记为“需要重新运行”,并在合适的时机(通常是下一个微任务)重新执行它。 - 可停止
副作用可以被手动或自动停止(组件卸载时)。停止后,它不再响应任何数据变化。
一些概念
副作用实例 ReactiveEffect
在 Vue 3 的响应式系统中,ReactiveEffect 是代表一个副作用(Effect) 的核心类。任何需要“在响应式数据变化时自动重新执行”的逻辑,都会被封装成一个 ReactiveEffect 实例。
依赖容器 Dep
在 Vue 3 的响应式系统中,Dep 是 Dependency(依赖) 的缩写,它代表一个依赖容器。每个响应式数据(例如 ref 的 .value、reactive 对象的某个属性、computed 的计算结果)都对应一个唯一的 Dep 实例。Dep 负责存储所有依赖该数据的副作用(ReactiveEffect) ,并在数据变化时通知它们更新。
双向链表节点 Link
在 Vue 3 的响应式系统中,Link 是一个核心的数据结构,用于连接 ReactiveEffect(副作用) 和 Dep(依赖容器) ,形成双向链表。每个 Link 节点同时存在于 effect.deps 链表(记录该 effect 依赖的所有 Dep)和 dep.subs 链表(记录依赖该 Dep 的所有 effect)。这种设计使得依赖关系的增删和更新操作都能在 O(1) 时间内完成,并且为高效的增量依赖清理提供了基础。
Dep.version 与 Link.version 协作机制
Dep.version:每次该数据被修改(通过 set 陷阱),dep.version 会自增 1。
Link.version:Effect 上一次看到的版本。
步骤1: effect 准备运行(prepareDeps)
Vue 在每次执行 effect 前,会遍历该 effect 的 deps 链表,将所有 Link.version 设置为 -1。
步骤2: effect 执行(run 中的 fn)
- 当 effect 内的函数访问某个响应式数据时,会触发
track。 track会找到或创建与该Dep和当前Effect关联的Link节点。- 如果
Link已存在且其version === -1(本轮尚未访问),则将其version更新为当前dep.version(一个正数),并将该Link移动到 effect 依赖链表的尾部(LRU 优化)。 - 如果
Link是新创建的,version直接设置为dep.version。
- 如果
- 这样,本次运行中被访问过的依赖,其
Link.version被更新为dep.version(不再是 -1)。
Vue 3 维护 effect.deps 链表时采用了 LRU(最近最少使用) 策略:
- 链表尾部:存放本次 effect 执行中最近被访问的依赖。
- 链表头部:存放可能已过时的依赖(即上次执行后未被再次访问的依赖)。
移动 link 到尾部的目的,是在 effect 执行结束后,能够高效地清理无效依赖:
- 清理阶段(
cleanupDeps)会从链表头部开始遍历,遇到第一个version !== -1的节点时停止。 - 由于被访问过的依赖都被移到了尾部,头部连续区域全是
version === -1的废弃节点,清理时只需删除头部一段连续的节点,而无需扫描整个链表。
步骤3: effect 执行完毕(cleanupDeps)
- 再次遍历 effect 的
deps链表,检查每个Link.version:- 如果
Link.version === -1→ 该依赖在本次执行中没有被访问 → 调用removeSub(link)将其从Dep.subs和Effect.deps中移除(即清理失效依赖)。 - 如果
Link.version !== -1→ 依赖仍然有效,保留在链表中。
- 如果
function cleanupDeps(sub: Subscriber) {
// Cleanup unused deps
let head // 用于记录新的依赖链表头部
let tail = sub.depsTail // 从订阅者的 depsTail 开始,即依赖链表的尾部
let link = tail // 当前处理的依赖链接,初始化为 tail
// 逆序遍历依赖链表
while (link) {
const prev = link.prevDep
// 在 prepareDeps 函数中,所有依赖的版本号会被重置为 -1
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
// 复依赖的 activeLink 为之前保存的 prevActiveLink
link.dep.activeLink = link.prevActiveLink
link.prevActiveLink = undefined // 清除 prevActiveLink,避免内存泄漏
link = prev
}
// set the new head & tail
sub.deps = head
sub.depsTail = tail
}
effect API
Vue3 响应式系统的核心机制,负责创建一个响应式副作用(Reactive Effect),并立即执行一次,返回一个可以手动触发该副作用的 runner 函数。
语法
// 语法
const runner = effect(fn,options:ReactiveEffectOptions)
interface ReactiveEffectOptions extends DebuggerOptions {
// 调度器函数,用于在依赖变化时调用
scheduler?: EffectScheduler
// 是否允许递归调用
allowRecurse?: boolean
// 停止函数,用于在订阅者被移除时调用
onStop?: () => void
}
示例 多个副作用 关联同一个响应式数据
import { effect, ref } from "vue";
const color = ref(0);
const count = ref(0);
const title = ref("云平台首页新");
const handleClick = () => {
count.value++;
};
/* 情况一: 多个 effect 订阅 同一个响应式变量 */
// 副作用函数、监听 count变化
effect(() => {
console.log("effect1 count 值", count.value);
});
// 副作用函数、监听 count变化
effect(() => {
console.log("effect2 count 值", count.value);
});
执行 第一个 effect , 新增双向链表 link 结构示意图
- 第一步:创建
count Ref响应式数据,初始化dep = new Dep() - 第二步:执行
effect, 创建副作用ReactiveEffect(即effect1) - 第三步:执行
effect1副作用的回调函数,count.value触发依赖收集 - 第四步:执行
countRef.dep.track, 触发依赖收集,创建双向链表Link(sub,dep) - 第五步:建立
effect1和link的关联 - 第六步:建立
dep和link的关联
执行 第二个 effect , 新增双向链表 link 结构示意图
- 第一步:执行
effect, 创建副作用ReactiveEffect(即effect2) - 第二步:执行
effect2副作用的回调函数,count.value触发依赖收集 - 第三步:执行
countRef.dep.track, 触发依赖收集,创建双向链表Link2(effect2,dep) - 第四步:建立
effect2和link2的关联 - 第五步:建立
dep和link2的关联
点击按钮触发依赖更新,从 dep.subs 尾指针开始向前遍历。
触发 trigger 函数,从 dep.subs (订阅者链表尾指针 往前遍历),触发 link.sub.notify()
遍历双向链表指针,将 effect 添加到普通订阅者的批处理队列头
示例 一个 effect 关联多个响应式数据
const color = ref(0);
const count = ref(0);
const handleClick = () => {
count.value++;
};
effect(() => {
console.log("effect1 color 值", color.value);
console.log("effect1 count 值", count.value);
});
执行过程:
- 创建
color Ref对象,初始化this.dep = new Dep() - 创建
count Ref对象,初始化this.dep = new Dep() - 创建
effect副作用 - 执行副作用回调函数
- 读取
color.value,触发依赖收集,创建link双向链表,关联effect和colorRef的依赖链表 - 读取
count.value,触发依赖收集,创建link1双向链表,关联effect和countRef的依赖链表 - 后创建的双向链表
Link(如link1) 会在effect的依赖链表尾部。
- 读取
点击按钮,触发响应式通知
示例 手动控制
effect 返回一个 runner 函数,可用于手动触发 effect 的执行
<template>
<div>
<h2>手动控制</h2>
<button @click="color++">点击</button>
<button @click="handleClick">监听</button>
<button @click="handleStop">取消监听</button>
</div>
</template>
<script setup lang="ts">
import { effect, ref } from "vue";
const color = ref(0);
// 返回 runner 函数,用于手动触发监听
const runner = effect(() => {
console.log("color 值", color.value);
});
// 手动执行不受依赖变化影响
const handleClick = () => {
runner();
};
const handleStop = () => {
runner.effect.stop();
};
</script>
执行 runner.effect.stop()
示例 停止 effect
runner.effect.stop() 不会阻止手动调用 runner() 函数,但会影响其行为:
- 手动调用仍然有效:即使调用了
stop(),仍然可以通过runner()手动执行副作用函数 - 依赖追踪被禁用:手动调用时不会再追踪依赖关系,也不会在依赖变化时自动重新执行
<template>
<div>
<button @click="color++">点击</button>
<button @click="handleClick">监听</button>
<button @click="handleClick2">手动执行</button>
</div>
</template>
<script setup lang="ts">
import { effect, ref } from "vue";
const color = ref(0);
// 返回 runner 函数,用于手动触发监听
const runner = effect(() => {
// color 变化时,会触发监听函数
console.log("color 值", color.value);
});
// 手动执行不受依赖变化影响
// 即使取消监听,手动执行仍会触发
const handleClick = () => {
runner();
};
const handleClick2 = () => {
// 停止effect监听
runner.effect.stop();
};
</script>
示例 effect reactive对象
<template>
<div>effect B</div>
</template>
<script lang="ts" setup>
import { effect, reactive } from "vue";
const obj = reactive({
count: 0,
name: "effectB",
age: 20,
});
effect(() => {
console.log("effectB 值", obj.count, obj.name, obj.age);
});
</script>
effect 读取 obj.count
effect 读取 obj.name
之前读取 obj.count时, target(即 reactive obj 对象) 已经缓存到 targetMap。
给属性 count 设置 dep 依赖对象。
执行 dep.track
dep 是新创建的,因此需要创建 new Link(sub,dep) 双向依赖链表。
示例 effect computed
<template>
<div>
<p id="cdom">count 值</p>
<button @click="handleClick">点击</button>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, effect } from "vue";
const count = ref(0);
const handleClick = () => {
count.value++;
};
const doubleCount = computed(() => count.value * 2);
console.log(doubleCount.value);
effect(() => {
console.log("doubleCount", doubleCount.value);
});
</script>
执行 count.value++
执行 computedEffect 的 notify(),再执行 computed.dep.notify()
继续执行 effect.notify()
示例 effect reactive数组
const reactiveArr = reactive(["a", "b", "c"]);
effect(() => {
console.log("reactiveArr", reactiveArr[0], reactiveArr[1]);
});
示例 watch 批量更新
<template>
<div>
<button @click="handleClick">点击</button>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from "vue";
const count = ref(0);
const handleClick = () => {
// 批量更新,触发 watch 一次
count.value++;
count.value++;
count.value++;
};
watch(count, (newVal, oldVal) => {
console.log("watch count 值", newVal, oldVal);
});
</script>
第一次执行 count.vauel++
- 获取
count.value - 执行
count.value自增,触发 countRef 的dep.trigger,然后执行dep.notify,这里会进行批处理,startBatch,执行相应effect.notify(将副作用实例加入队列 batchedSub),结束处理 批处理结束endBatch。 - endBatch 会执行
effect.tigger方法,由于watch effect实例有 调度器 scheduler,因此执行effect.scheduler. - 调度器执行
queueJob函数任务 job加入 队列queue,标记job.flag为已加入队列。暂不执行。 - 执行 queueFlush,调度一个微任务队列。
第二次 执行 count.vauel++
- 获取
count.value - 执行
count.value自增,触发 countRef 的dep.trigger,然后执行dep.notify,这里会进行批处理,startBatch,执行相应effect.notify(将副作用实例加入队列 batchedSub),结束处理 批处理结束endBatch。 - endBatch 会执行
effect.tigger方法,由于watch effect实例有 调度器 scheduler,因此执行effect.scheduler. - 调度器执行
queueJob函数任务 job加入 队列queue,由于job 已在队列中,不再重复加入。
第三次 执行 count.vauel++
- 获取
count.value - 执行
count.value自增,触发 countRef 的dep.trigger,然后执行dep.notify,这里会进行批处理,startBatch,执行相应effect.notify(将副作用实例加入队列 batchedSub),结束处理 批处理结束endBatch。 - endBatch 会执行
effect.tigger方法,由于watch effect实例有 调度器 scheduler,因此执行effect.scheduler. - 调度器执行
queueJob函数任务 job加入 队列queue,由于job 已在队列中,不再重复加入。 - 微任务执行
flushJobs,按顺序执行任务队列,先执行主队列 queue,再执行后置任务队列 pendingPostFlushCbs,最后还会检查是否有新任务入队,有则递归处理。
枚举 SchedulerJobFlags
enum SchedulerJobFlags {
QUEUED = 1 << 0, // 1 标记已经加入队列
PRE = 1 << 1, // 2 标记任务在 DOM 更新前 执行
ALLOW_RECURSE = 1 << 2, // 4 允许自身递归
DISPOSED = 1 << 3, // 已被销毁或已取消
}
track 源码
reactive 对象读取属性,依赖收集。
function track(target: object, type: TrackOpTypes, key: unknown): void {
// 前置条件:是否需要收集依赖
// - shouldTrack:全局标志,控制是否允许追踪依赖。
// - activeSub:当前正在执行的 ReactiveEffect 实例(副作用)
if (shouldTrack && activeSub) {
// targetMap:全局 Map,存储所有响应式对象的依赖映射
let depsMap = targetMap.get(target)
if (!depsMap) {
// 如果该 target 第一次被追踪,则创建一个新的 Map 并存入 targetMap。
targetMap.set(target, (depsMap = new Map()))
}
// 获取或创建依赖对象
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Dep()))
dep.map = depsMap // 存储依赖映射表,用于触发依赖时快速查找
dep.key = key // 存储属性名,用于触发依赖时快速查找
}
if (__DEV__) {
dep.track({
target,
type,
key,
})
} else {
// 收集依赖
dep.track()
}
}
}
trigger 源码
function trigger(
target: object, // 原始对象(未被代理的原对象)
type: TriggerOpTypes, // 操作类型:SET | ADD | DELETE | CLEAR
key?: unknown, // 被修改的属性名
newValue?: unknown, // 新值
oldValue?: unknown, // 旧值
oldTarget?: Map<unknown, unknown> | Set<unknown>, // 用于集合(Map/Set)的旧对象
): void {
const depsMap = targetMap.get(target)
if (!depsMap) {
// never been tracked
// 如果 target 从未被追踪过(没有 depsMap),则仅增加全局版本号
globalVersion++
return
}
const run = (dep: Dep | undefined) => {
if (dep) {
if (__DEV__) {
dep.trigger({
target,
type,
key,
newValue,
oldValue,
oldTarget,
})
} else {
// 触发依赖,进行通知
dep.trigger()
}
}
}
// 批处理期间会收集需要执行的 effect,最后统一执行。
// 开始批处理
startBatch()
// 1、清空集合,如 Map.clear() / Set.clear()
if (type === TriggerOpTypes.CLEAR) {
// collection being cleared
// trigger all effects for target
depsMap.forEach(run)
} else {
const targetIsArray = isArray(target)
const isArrayIndex = targetIsArray && isIntegerKey(key)
// 2、处理数组的 length 属性变化(特殊逻辑)
if (targetIsArray && key === 'length') {
const newLength = Number(newValue)
depsMap.forEach((dep, key) => {
if (
key === 'length' ||
key === ARRAY_ITERATE_KEY ||
(!isSymbol(key) && key >= newLength)
) {
run(dep)
}
})
} else {
// schedule runs for SET | ADD | DELETE
// 如果 key 是具体的属性名(不是 undefined),则触发该属性对应的依赖。
// 或者存在一个无 key 的依赖(void 0),也需要触发
// 例如 watchEffect 中直接读取整个 reactive 对象,依赖会挂在 void 0 上。
if (key !== void 0 || depsMap.has(void 0)) {
run(depsMap.get(key))
}
// schedule ARRAY_ITERATE for any numeric key change (length is handled above)
// 如果当前 key 是数组索引(key 为整数),还需要触发数组迭代器依赖
if (isArrayIndex) {
run(depsMap.get(ARRAY_ITERATE_KEY))
}
// also run for iteration key on ADD | DELETE | Map.SET
switch (type) {
// 添加新属性
case TriggerOpTypes.ADD:
if (!targetIsArray) {
// 非数组时:触发 ITERATE_KEY(因为 for...in 循环会新增键)
run(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
// 如果是 Map:触发 MAP_KEY_ITERATE_KEY(Map.keys() 迭代器)
run(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isArrayIndex) {
// new index added to array -> length changes
// 如果是数组且是数组索引(添加新索引):触发 length 的依赖(因为数组长度增加了)
run(depsMap.get('length'))
}
break
// 删除属性
case TriggerOpTypes.DELETE:
if (!targetIsArray) {
run(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
run(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
// 设置属性值
case TriggerOpTypes.SET:
if (isMap(target)) {
// 仅当是 Map 时:触发 ITERATE_KEY
// 因为 Map 的 forEach 或 entries 迭代器需要知道值变化
run(depsMap.get(ITERATE_KEY))
}
break
}
}
}
endBatch()
}
Dep 源码
class Dep {
version = 0
activeLink?: Link = undefined
subs?: Link = undefined
subsHead?: Link
map?: KeyToDepMap = undefined
key?: unknown = undefined
sc: number = 0
readonly __v_skip = true
constructor(public computed?: ComputedRefImpl | undefined) {
if (__DEV__) {
this.subsHead = undefined
}
}
// 示例:当执行 computed.value会触发依赖收集
// 示例:当执行 ref.value 会触发依赖收集
// 建立当前活动的副作用(activeSub)与当前依赖(Dep)之间的双向链接,
track(debugInfo?: DebuggerEventExtraInfo): Link | undefined {
// 一、当满足以下任一条件时,会直接返回,不进行依赖追踪
// 1、没有活动的订阅者(没有正在运行的副作用(例如在非响应式上下文中访问),不收集)
// 2、不跟踪依赖
// 3、订阅者是否为当前依赖关联的计算属性。
// 作用:防止计算属性的循环依赖。场景:当计算属性的 getter 函数访问自身时,避免形成循环依赖。
if (!activeSub || !shouldTrack || activeSub === this.computed) {
return
}
// 二、获取或创建当前 dep 与 activeSub 的链接(Link)
let link = this.activeLink // 当前活动 link
// 没有活动link、或 活动link 不是当前订阅者
// 作用:确保只为当前订阅者建立链接,避免重复或错误的链接
if (link === undefined || link.sub !== activeSub) {
// 创建新的 Link 实例
link = this.activeLink = new Link(activeSub, this)
// add the link to the activeEffect as a dep (as tail)
// 将 link 添加到 订阅者的依赖链表
if (!activeSub.deps) {
activeSub.deps = activeSub.depsTail = link
} else {
// 2、如果链表中已经有节点,将 link 添加到链表尾尾
link.prevDep = activeSub.depsTail
activeSub.depsTail!.nextDep = link
activeSub.depsTail = link
}
// 将链接添加到依赖者的订阅者链表
addSub(link)
// link 版本-1 ,说明这是第一次访问依赖,需要同步版本号
} 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
}
// 触发依赖更新
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()) {
;(link.sub as ComputedRefImpl).dep.notify()
}
}
} finally {
endBatch()
}
}
}
Link 源码
class Link {
version: number
nextDep?: Link
prevDep?: Link
nextSub?: Link
prevSub?: Link
prevActiveLink?: Link // 存储之前的活动链接
constructor(
public sub: Subscriber, // 作为当前实例的sub
public dep: Dep, // 作为当前实例的dep
) {
this.version = dep.version // 初始化为当前依赖的版本号
this.nextDep =
this.prevDep =
this.nextSub =
this.prevSub =
this.prevActiveLink =
undefined
}
}
ReactiveEffect 源码
class ReactiveEffect<T = any>
implements Subscriber, ReactiveEffectOptions
{
deps?: Link = undefined
depsTail?: Link = undefined
flags: EffectFlags = EffectFlags.ACTIVE | EffectFlags.TRACKING
next?: Subscriber = undefined
cleanup?: () => void = undefined
scheduler?: EffectScheduler = undefined
onStop?: () => void
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
constructor(public fn: () => T) {
if (activeEffectScope && activeEffectScope.active) {
activeEffectScope.effects.push(this)
}
}
pause(): void {
this.flags |= EffectFlags.PAUSED
}
// 恢复当前 effect 实例
resume(): void {
if (this.flags & EffectFlags.PAUSED) {
this.flags &= ~EffectFlags.PAUSED
if (pausedQueueEffects.has(this)) {
pausedQueueEffects.delete(this)
this.trigger()
}
}
}
/**
* @internal
*/
notify(): void {
if (
// 当前 effect 实例正在运行中,且不允许递归调用
this.flags & EffectFlags.RUNNING &&
!(this.flags & EffectFlags.ALLOW_RECURSE)
) {
return
}
// 当前 effect 实例未被通知过
if (!(this.flags & EffectFlags.NOTIFIED)) {
batch(this) // 加入订阅者队列
}
}
run(): T {
// TODO cleanupEffect
if (!(this.flags & EffectFlags.ACTIVE)) {
// stopped during cleanup
// 调用当前 effect 实例的 fn 函数
return this.fn()
}
// 标记当前 effect 实例为正在运行
this.flags |= EffectFlags.RUNNING
cleanupEffect(this) // 清理当前 effect 实例的依赖链表
prepareDeps(this) // 准备当前 effect 实例的依赖链表
const prevEffect = activeSub
const prevShouldTrack = shouldTrack
activeSub = this // 设置当前实例为活跃订阅者
shouldTrack = true // 标记当前实例为正在追踪依赖
try {
// 调用当前 effect 实例的 fn 函数
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 // 恢复上一个 shouldTrack 状态
// 移除当前 effect 实例正在运行的标志位
this.flags &= ~EffectFlags.RUNNING
}
}
// 停止当前 effect 实例
stop(): void {
// 如果当前 effect 实例正在运行中
if (this.flags & EffectFlags.ACTIVE) {
// 移除当前 effect 实例的所有依赖
for (let link = this.deps; link; link = link.nextDep) {
removeSub(link)
}
// 清理当前 effect 实例的依赖链表
this.deps = this.depsTail = undefined
// 清理当前 effect 实例的依赖链表
cleanupEffect(this)
// 调用当前 effect 实例的 onStop 回调
this.onStop && this.onStop()
// 移除当前 effect 实例活跃的标志位
this.flags &= ~EffectFlags.ACTIVE
}
}
// 触发当前 effect 执行,根据调度器函数或直接运行
trigger(): void {
// 如果当前 effect 实例正在暂停中
if (this.flags & EffectFlags.PAUSED) {
// 将当前 effect 实例添加到暂停队列中
pausedQueueEffects.add(this)
// 调用当前 effect 实例的 scheduler 函数
} else if (this.scheduler) {
this.scheduler()
} else {
// 如果当前 effect 实例脏了
this.runIfDirty()
}
}
/**
* @internal
*/
runIfDirty(): void {
if (isDirty(this)) {
this.run()
}
}
get dirty(): boolean {
return isDirty(this)
}
}