```js
// -----------------------------
// Imports / Types
// -----------------------------
// EN: import helper utilities: `extend` to copy properties, `hasChanged` to compare values.
// CN: 导入工具函数:`extend` 用于合并/复制属性,`hasChanged` 用于比较值是否变化。
import { extend, hasChanged } from '@vue/shared'
// EN: import the ComputedRefImpl type to allow refreshComputed to access internals.
// CN: 导入 ComputedRefImpl 类型,使 refreshComputed 能访问 computed 的内部字段。
import type { ComputedRefImpl } from './computed'
// EN: import types for track/trigger op kinds (used in debugger event types).
// CN: 导入 track/trigger 操作类型,用于调试器事件类型定义。
import type { TrackOpTypes, TriggerOpTypes } from './constants'
// EN: Link type and globalVersion used by dep implementation.
// CN: Link 类型与 globalVersion,供 dep 实现使用。
import { type Link, globalVersion } from './dep'
// EN: activeEffectScope is used to register created effects to a scope for cleanup.
// CN: activeEffectScope 用于将创建的 effect 注册到当前的 effectScope,便于后续集中清理。
import { activeEffectScope } from './effectScope'
// EN: warn helper for dev-only warnings.
// CN: 开发环境使用的警告函数。
import { warn } from './warning'
// -----------------------------
// Type aliases & interfaces
// -----------------------------
// EN: Scheduler type for custom scheduling of effect runs.
// CN: effect 调度器类型,允许用户自定义何时执行 effect。
export type EffectScheduler = (...args: any[]) => any
// EN: Debugger event contains the effect (subscriber) plus extra info about the operation.
// CN: 调试事件,包含 effect(subscriber)以及关于操作的额外信息。
export type DebuggerEvent = { effect: Subscriber } & DebuggerEventExtraInfo
export type DebuggerEventExtraInfo = {
target: object
type: TrackOpTypes | TriggerOpTypes
key: any
newValue?: any
oldValue?: any
oldTarget?: Map<any, any> | Set<any>
}
// EN: Debugger hooks for track/trigger events.
// CN: 调试选项:在 track/trigger 时会调用的钩子。
export interface DebuggerOptions {
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
}
// EN: Options for ReactiveEffect: scheduler, allowRecuse, onStop, plus debugger hooks.
// CN: ReactiveEffect 可配置项:scheduler、allowRecurse、onStop,以及调试钩子。
export interface ReactiveEffectOptions extends DebuggerOptions {
scheduler?: EffectScheduler
allowRecurse?: boolean
onStop?: () => void
}
// EN: Runner function returned by `effect`, with a reference to its effect instance.
// CN: `effect` 返回的 runner 函数类型,同时包含 `.effect` 指向 ReactiveEffect 实例。
export interface ReactiveEffectRunner<T = any> {
(): T
effect: ReactiveEffect
}
// EN: The active subscriber (effect/computed) at runtime.
// CN: 当前处于活跃状态的订阅者(可能是 effect 或 computed)。
export let activeSub: Subscriber | undefined
// -----------------------------
// Effect Flags (bitmask) - compact state flags
// -----------------------------
// EN: Bit flags to track effect/computed state efficiently.
// CN: 使用位掩码表示 effect 的各种状态,便于快速判断与组合状态。
export enum EffectFlags {
/** ReactiveEffect only */
ACTIVE = 1 << 0,
RUNNING = 1 << 1,
TRACKING = 1 << 2,
NOTIFIED = 1 << 3,
DIRTY = 1 << 4,
ALLOW_RECURSE = 1 << 5,
PAUSED = 1 << 6,
EVALUATED = 1 << 7,
}
// -----------------------------
// Subscriber interface (internal)
// -----------------------------
// EN: Subscriber represents anything that subscribes to deps (effects, computed).
// CN: Subscriber 表示订阅依赖的实体(例如 effect 或 computed)。
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
}
// -----------------------------
// ReactiveEffect class
// -----------------------------
// EN: A set to collect effects that were paused and should be triggered once resumed.
// CN: 用于缓存处于 PAUSED 状态且等待恢复后触发的 effects 的集合(弱引用,方便 GC)。
const pausedQueueEffects = new WeakSet<ReactiveEffect>()
// EN: ReactiveEffect implements Subscriber and holds effect runtime data and methods.
// CN: ReactiveEffect 实现 Subscriber,保存运行时数据与方法(run/stop/trigger 等)。
export class ReactiveEffect<T = any> implements Subscriber, ReactiveEffectOptions {
/** @internal */ deps?: Link = undefined
/** @internal */ depsTail?: Link = undefined
/** @internal */ flags: EffectFlags = EffectFlags.ACTIVE | EffectFlags.TRACKING
/** @internal */ next?: Subscriber = undefined
/** @internal */ cleanup?: () => void = undefined
// EN: optional scheduler and lifecycle/debug hooks
// CN: 可选的调度器与生命周期/调试钩子
scheduler?: EffectScheduler = undefined
onStop?: () => void
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
// EN: constructor registers effect to active effect scope if any, and stores fn.
// CN: 构造函数:如存在 activeEffectScope,会将该 effect 注册进去;并保存传入的 fn。
constructor(public fn: () => T) {
if (activeEffectScope && activeEffectScope.active) {
activeEffectScope.effects.push(this)
}
}
// EN: Mark the effect as paused (it will not run immediately on trigger).
// CN: 将 effect 标记为 PAUSED(触发时不会立即运行,改为放入 pausedQueueEffects 中)。
pause(): void {
this.flags |= EffectFlags.PAUSED
}
// EN: Resume the effect: if it was queued while paused, run it now.
// CN: 恢复 effect;若此前因为暂停被加入 pausedQueueEffects,会立刻触发一次。
resume(): void {
if (this.flags & EffectFlags.PAUSED) {
this.flags &= ~EffectFlags.PAUSED
if (pausedQueueEffects.has(this)) {
pausedQueueEffects.delete(this)
this.trigger()
}
}
}
/** @internal */
// EN: notify is called by deps to schedule this effect for execution.
// CN: notify 由 dep 调用以安排 effect 的执行(通过批处理机制或 scheduler)。
notify(): void {
if (
this.flags & EffectFlags.RUNNING &&
!(this.flags & EffectFlags.ALLOW_RECURSE)
) {
// EN: if currently running and recursion not allowed, skip.
// CN: 若正在运行且不允许递归,则忽略这次通知,避免无限递归。
return
}
if (!(this.flags & EffectFlags.NOTIFIED)) {
// EN: mark as notified and add to batch queue
// CN: 标记为 NOTIFIED 并加入批处理队列
batch(this)
}
}
// EN: Run the effect function immediately (collect deps). Returns the result of fn().
// CN: 立即执行 effect 函数并收集依赖,返回 fn 的执行结果。
run(): T {
// TODO cleanupEffect
// EN: if effect is not active (stopped), still run fn but without tracking.
// CN: 若 effect 已停止(ACTIVE 标志清除),则直接运行 fn(不进行依赖收集)。
if (!(this.flags & EffectFlags.ACTIVE)) {
// stopped during cleanup
return this.fn()
}
// EN: mark running, prepare and cleanup deps before/after run
// CN: 标记为 RUNNING,清理上次遗留的 effect 外部状态,准备 deps 以便收集。
this.flags |= EffectFlags.RUNNING
cleanupEffect(this)
prepareDeps(this)
const prevEffect = activeSub
const prevShouldTrack = shouldTrack
activeSub = this
shouldTrack = true
try {
return this.fn()
} finally {
// EN: restore active effect and tracking state; cleanup any deps marked unused.
// CN: 恢复先前的 activeSub 与 shouldTrack,清理未被使用的 deps,并清除 RUNNING 标记。
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
}
}
// EN: stop the effect and remove it from all deps; call onStop hook.
// CN: 停止 effect:从所有 dep 中移除自己,执行 cleanup 与 onStop 回调,并清除 ACTIVE 标志。
stop(): void {
if (this.flags & EffectFlags.ACTIVE) {
for (let link = this.deps; link; link = link.nextDep) {
removeSub(link)
}
this.deps = this.depsTail = undefined
cleanupEffect(this)
this.onStop && this.onStop()
this.flags &= ~EffectFlags.ACTIVE
}
}
// EN: when triggered, either queue (if paused), call scheduler, or runIfDirty.
// CN: trigger 时的行为:若 PAUSED,则排队;若有 scheduler 用 scheduler;否则按脏检查执行。
trigger(): void {
if (this.flags & EffectFlags.PAUSED) {
pausedQueueEffects.add(this)
} else if (this.scheduler) {
this.scheduler()
} else {
this.runIfDirty()
}
}
/** @internal */
// EN: run effect only if it's considered dirty by `isDirty`.
// CN: 仅当 isDirty 返回 true(依赖发生变化或版本不同等)时调用 run。
runIfDirty(): void {
if (isDirty(this)) {
this.run()
}
}
// EN: query property to check if effect is dirty.
// CN: 通过 isDirty 检查 effect 是否"脏"(需要重新执行)。
get dirty(): boolean {
return isDirty(this)
}
}
// -----------------------------
// Batching and queue handling
// -----------------------------
// EN: depth of nested batches; batching defers triggers until matching endBatch.
// CN: 批处理深度计数;允许嵌套批处理,只有当最外层 endBatch 被调用时才真正触发队列中的 effects。
let batchDepth = 0
let batchedSub: Subscriber | undefined
let batchedComputed: Subscriber | undefined
// EN: Add a subscriber to the batch queue. Computed effects are queued separately.
// CN: 将 subscriber 加入批处理队列。computed 类型单独队列以保证更新顺序/语义。
export function batch(sub: Subscriber, isComputed = false): void {
sub.flags |= EffectFlags.NOTIFIED
if (isComputed) {
sub.next = batchedComputed
batchedComputed = sub
return
}
sub.next = batchedSub
batchedSub = sub
}
/** @internal */
// EN: Start a batch: increase nesting depth.
// CN: 开始一个批处理(深度 +1)。
export function startBatch(): void {
batchDepth++
}
/** Run batched effects when all batches have ended @internal */
// EN: End batch: if outermost, flush queued computed first (clear notified) then normal effects.
// CN: 结束批处理:若为最外层,则先处理 batchedComputed(仅清理状态),再处理 batchedSub 并触发正常 effects。
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
}
// -----------------------------
// Dep tracking helpers
// -----------------------------
// EN: prepareDeps marks existing links with version -1 and saves previous activeLink
// so that after running the effect we can detect which deps were not re-used.
// CN: prepareDeps 将现有依赖链(link)的 version 标为 -1,并保存原先的 activeLink。
// 运行后通过版本判断哪些依赖未被重新使用(用于移除过时订阅)。
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,以便在执行后识别未使用的 dep
link.version = -1
// store previous active sub if link was being used in another context
// 保存之前 link.dep 的 activeLink(如果该 link 被其他 subscriber 使用)
link.prevActiveLink = link.dep.activeLink
// 将 dep 的 activeLink 指向当前 link,以标识当前 link 为激活状态
link.dep.activeLink = link
}
}
// EN: cleanupDeps traverses from tail to head and removes links whose version remained -1
// (i.e., not used in latest run). Also restores dep.activeLink to previous.
// CN: cleanupDeps 从尾向头遍历;对于仍为 -1 的 link(未在此次运行中使用),从 dep 的订阅列表移除。
// 同时恢复 dep.activeLink 为之前保存的值。
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
// 未使用:从 dep 的订阅者列表中移除
removeSub(link)
// also remove it from this effect's dep list
// 也从当前 effect 的 dep 双链表中移除该 link
removeDep(link)
} else {
// The new head is the last node seen which wasn't removed
// 新的 head 为最后一个未被移除的节点(从尾向前)
head = link
}
// restore previous active link if any
// 恢复 link.dep 的 activeLink 为保存的 prevActiveLink
link.dep.activeLink = link.prevActiveLink
link.prevActiveLink = undefined
link = prev
}
// set the new head & tail
// 更新 effect 的 deps head / tail 引用
sub.deps = head
sub.depsTail = tail
}
// EN: isDirty determines if a subscriber should re-run by checking deps' version or computed status.
// CN: isDirty 检查 subscriber 是否"脏":遍历其 deps,若 dep.version 与 link.version 不匹配,或者对应的 computed 需要刷新则为脏。
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
// EN: backward-compat check for libraries that manually set `_dirty`.
// CN: 向后兼容:某些库(如 Pinia 测试模块)可能手动设置 `_dirty`。
if (sub._dirty) {
return true
}
return false
}
// -----------------------------
// Computed refresh logic
// -----------------------------
/**
* Returning false indicates the refresh failed
* @internal
*/
export function refreshComputed(computed: ComputedRefImpl): undefined {
// EN: If computed is tracking and not marked dirty, no need to refresh.
// CN: 若 computed 正在 TRACKING 且未标记为 DIRTY,则无需刷新。
if (
computed.flags & EffectFlags.TRACKING &&
!(computed.flags & EffectFlags.DIRTY)
) {
return
}
// EN: clear DIRTY flag since we are about to recompute/validate.
// CN: 清除 DIRTY 标志(准备刷新或验证)。
computed.flags &= ~EffectFlags.DIRTY
// EN: fast path: if no global reactive change (globalVersion unchanged), skip.
// CN: 全局版本快速路径:若 computed.globalVersion 与全局 globalVersion 相同,说明自上次计算后没有全局 reactive 变更,直接跳过。
if (computed.globalVersion === globalVersion) {
return
}
computed.globalVersion = globalVersion
// EN: SSR / no-subscriber / already evaluated handling:
// If there's no SSR and computed was evaluated before and either has no deps
// or isn't dirty, we can skip re-evaluation.
// CN: SSR 情况(服务端渲染没有 render effect)、无 deps 或已经评估且不脏时可以跳过重新评估。
if (
!computed.isSSR &&
computed.flags & EffectFlags.EVALUATED &&
((!computed.deps && !(computed as any)._dirty) || !isDirty(computed))
) {
return
}
// EN: mark as running and set up activeSub/shouldTrack to collect deps while evaluating.
// CN: 将 computed 标记为 RUNNING,并设置 activeSub、shouldTrack 以便在求值时收集依赖。
computed.flags |= EffectFlags.RUNNING
const dep = computed.dep
const prevSub = activeSub
const prevShouldTrack = shouldTrack
activeSub = computed
shouldTrack = true
try {
// EN: evaluate computed.fn with previous value (some computed accept oldValue).
// CN: 执行 computed.fn,可带入旧值作为参数(实现细节依赖具体 computed 实现)。
prepareDeps(computed)
const value = computed.fn(computed._value)
// EN: if first time (dep.version===0) or value changed, set EVALUATED and update _value.
// CN: 若是首次或值发生变化,设置 EVALUATED,将新值赋给 _value,并增加 dep.version。
if (dep.version === 0 || hasChanged(value, computed._value)) {
computed.flags |= EffectFlags.EVALUATED
computed._value = value
dep.version++
}
} catch (err) {
// EN: ensure version increments on error to avoid silent stale caches.
// CN: 出错时也增加 dep.version,避免缓存停滞导致数据不更新。
dep.version++
throw err
} finally {
// EN: restore active effect and tracking state, cleanup deps, clear RUNNING flag.
// CN: 恢复 activeSub 与 shouldTrack,清理 deps,并清除 RUNNING 标志。
activeSub = prevSub
shouldTrack = prevShouldTrack
cleanupDeps(computed)
computed.flags &= ~EffectFlags.RUNNING
}
}
// -----------------------------
// Helpers to remove subscriptions / deps
// -----------------------------
// EN: removeSub unlinks a subscription link from both dep and the subscriber.
// `soft` indicates not to decrement sc or remove entry from dep.map (used when computed unsubscribes soft).
// CN: removeSub 将 link 从 dep 的订阅链表和 subscriber 的链表中解开。
// soft 参数用于 computed 的"软取消订阅"(不减少 dep.sc,也不从 dep.map 删除),以便 computed 能被 GC。
function removeSub(link: Link, soft = false) {
const { dep, prevSub, nextSub } = link
if (prevSub) {
prevSub.nextSub = nextSub
link.prevSub = undefined
}
if (nextSub) {
nextSub.prevSub = prevSub
link.nextSub = undefined
}
if (__DEV__ && dep.subsHead === link) {
// was previous head, point new head to next
// EN: dev-only: 更新 dep.subsHead(如果需要)。
// CN: 开发模式:若 link 是旧的 head,更新 head 指向 next。
dep.subsHead = nextSub
}
if (dep.subs === link) {
// was previous tail, point new tail to prev
// EN: 更新 dep.subs(tail 指针)。
// CN: 若 link 是 tail(dep.subs),将 tail 指向 prevSub。
dep.subs = prevSub
if (!prevSub && dep.computed) {
// EN: if computed and no tail after removal, unsubscribe it from all deps so it can be GCed.
// CN: 如果这是 computed 并且没有 prevSub(意味着 dep 变为空),对 computed 进行彻底"软取消订阅"以便 GC。
dep.computed.flags &= ~EffectFlags.TRACKING
for (let l = dep.computed.deps; l; l = l.nextDep) {
// here we are only "soft" unsubscribing because the computed still keeps
// referencing the deps and the dep should not decrease its sub count
// EN: 软取消订阅:不减 dep.sc
// CN: 软取消订阅(不减少 dep.sc),以保持 computed 仍引用 deps 的结构一致性。
removeSub(l, true)
}
}
}
if (!soft && !--dep.sc && dep.map) {
// #11979
// property dep no longer has effect subscribers, delete it
// this mostly is for the case where an object is kept in memory but only a
// subset of its properties is tracked at one time
// EN: 如果非 soft,且 subscriber count 减为 0,并且 dep.map 存在,则从 dep.map 删除该键,节省内存。
// CN: 当某属性再无订阅者时(并且 dep.map 存在),删除该属性的 dep 条目以释放内存。
dep.map.delete(dep.key)
}
}
// EN: removeDep unlinks a link from the subscriber's dep double-linked list.
// CN: removeDep 从 effect/computed 的 deps(双向链)中移除 link 链接。
function removeDep(link: Link) {
const { prevDep, nextDep } = link
if (prevDep) {
prevDep.nextDep = nextDep
link.prevDep = undefined
}
if (nextDep) {
nextDep.prevDep = prevDep
link.nextDep = undefined
}
}
// -----------------------------
// Public API: effect / stop
// -----------------------------
export interface ReactiveEffectRunner<T = any> {
(): T
effect: ReactiveEffect
}
export function effect<T = any>(
fn: () => T,
options?: ReactiveEffectOptions,
): ReactiveEffectRunner<T> {
// EN: If fn is already a runner (i.e., we passed the runner itself), extract its effect.fn
// CN: 允许把已有 runner 直接传入 effect,若传入的是 runner,则取出其内部的 fn。
if ((fn as ReactiveEffectRunner).effect instanceof ReactiveEffect) {
fn = (fn as ReactiveEffectRunner).effect.fn
}
// EN: create new ReactiveEffect, extend with options (scheduler, hooks...), run immediately.
// CN: 创建 ReactiveEffect 实例,扩展 options(合并到实例上),并立即执行一次以收集初始 deps。
const e = new ReactiveEffect(fn)
if (options) {
extend(e, options)
}
try {
e.run()
} catch (err) {
e.stop()
throw err
}
const runner = e.run.bind(e) as ReactiveEffectRunner
runner.effect = e
return runner
}
/**
* Stops the effect associated with the given runner.
*
* @param runner - Association with the effect to stop tracking.
*/
export function stop(runner: ReactiveEffectRunner): void {
// EN: stop wrapper: call stop on the underlying ReactiveEffect instance.
// CN: stop 的封装:调用 runner.effect.stop() 停止该 effect。
runner.effect.stop()
}
// -----------------------------
// Global tracking toggle utilities
// -----------------------------
/** @internal */
export let shouldTrack = true
const trackStack: boolean[] = []
/** Temporarily pauses tracking. */
// EN: pause tracking: push current state and set shouldTrack=false
// CN: 暂时暂停依赖收集:把当前 shouldTrack 压入栈,然后置 false。
export function pauseTracking(): void {
trackStack.push(shouldTrack)
shouldTrack = false
}
/** Re-enables effect tracking (if it was paused). */
// EN: enable tracking: push current state and set shouldTrack=true
// CN: 开启依赖收集(也会保存先前状态以便 later reset)。
export function enableTracking(): void {
trackStack.push(shouldTrack)
shouldTrack = true
}
/** Resets the previous global effect tracking state. */
// EN: restore previous shouldTrack from stack (or default to true).
// CN: 重置为上一次的 shouldTrack 状态(若无则默认为 true)。
export function resetTracking(): void {
const last = trackStack.pop()
shouldTrack = last === undefined ? true : last
}
// -----------------------------
// onEffectCleanup / cleanupEffect
// -----------------------------
/**
* Registers a cleanup function for the current active effect.
* The cleanup function is called right before the next effect run, or when the
* effect is stopped.
*
* Throws a warning if there is no current active effect. The warning can be
* suppressed by passing `true` to the second argument.
*
* @param fn - the cleanup function to be registered
* @param failSilently - if `true`, will not throw warning when called without
* an active effect.
*/
// EN: Register a cleanup callback associated with the current active ReactiveEffect.
// CN: 为当前活跃的 ReactiveEffect 注册 cleanup 回调(在下一次运行前或 stop 时会执行)。
export function onEffectCleanup(fn: () => void, failSilently = false): void {
if (activeSub instanceof ReactiveEffect) {
activeSub.cleanup = fn
} else if (__DEV__ && !failSilently) {
warn(
`onEffectCleanup() was called when there was no active effect` +
` to associate with.`,
)
}
}
// EN: Execute and clear effect cleanup (run without any active effect).
// CN: 执行 effect 的 cleanup(在无 activeEffect 的上下文中运行),然后清空 cleanup 引用。
function cleanupEffect(e: ReactiveEffect) {
const { cleanup } = e
e.cleanup = undefined
if (cleanup) {
// run cleanup without active effect
// EN: run the cleanup with activeSub temporarily unset to avoid accidental tracking.
// CN: 运行 cleanup 时临时清除 activeSub,防止 cleanup 内部访问响应式数据导致错误的依赖收集。
const prevSub = activeSub
activeSub = undefined
try {
cleanup()
} finally {
activeSub = prevSub
}
}
}