2023-10-27 22:25:09 +08:00
|
|
|
import { NOOP, extend } from '@vue/shared'
|
|
|
|
import type { ComputedRefImpl } from './computed'
|
|
|
|
import {
|
|
|
|
DirtyLevels,
|
|
|
|
type TrackOpTypes,
|
|
|
|
type TriggerOpTypes,
|
|
|
|
} from './constants'
|
|
|
|
import type { Dep } from './dep'
|
2021-07-07 21:07:19 +08:00
|
|
|
import { type EffectScope, recordEffectScope } from './effectScope'
|
2021-07-08 00:33:37 +08:00
|
|
|
|
2021-07-09 01:41:38 +08:00
|
|
|
export type EffectScheduler = (...args: any[]) => any
|
2018-11-14 00:03:35 +08:00
|
|
|
|
2019-10-22 23:53:17 +08:00
|
|
|
export type DebuggerEvent = {
|
2018-11-14 00:03:35 +08:00
|
|
|
effect: ReactiveEffect
|
2021-06-24 05:22:21 +08:00
|
|
|
} & DebuggerEventExtraInfo
|
|
|
|
|
|
|
|
export type DebuggerEventExtraInfo = {
|
2019-10-22 23:26:48 +08:00
|
|
|
target: object
|
2019-12-04 00:30:24 +08:00
|
|
|
type: TrackOpTypes | TriggerOpTypes
|
2019-10-22 23:26:48 +08:00
|
|
|
key: any
|
|
|
|
newValue?: any
|
|
|
|
oldValue?: any
|
|
|
|
oldTarget?: Map<any, any> | Set<any>
|
2018-11-14 00:03:35 +08:00
|
|
|
}
|
|
|
|
|
2022-01-30 18:52:23 +08:00
|
|
|
export let activeEffect: ReactiveEffect | undefined
|
2018-11-14 00:03:35 +08:00
|
|
|
|
2021-06-25 05:44:32 +08:00
|
|
|
export class ReactiveEffect<T = any> {
|
|
|
|
active = true
|
|
|
|
deps: Dep[] = []
|
2018-11-14 00:03:35 +08:00
|
|
|
|
2022-01-21 12:31:54 +08:00
|
|
|
/**
|
|
|
|
* Can be attached after creation
|
|
|
|
* @internal
|
|
|
|
*/
|
|
|
|
computed?: ComputedRefImpl<T>
|
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2021-07-08 12:31:26 +08:00
|
|
|
allowRecurse?: boolean
|
2022-01-21 12:31:54 +08:00
|
|
|
|
2021-06-25 05:44:32 +08:00
|
|
|
onStop?: () => void
|
|
|
|
// dev only
|
|
|
|
onTrack?: (event: DebuggerEvent) => void
|
|
|
|
// dev only
|
|
|
|
onTrigger?: (event: DebuggerEvent) => void
|
2019-06-12 00:03:50 +08:00
|
|
|
|
2023-10-27 22:25:09 +08:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
|
|
|
_dirtyLevel = DirtyLevels.Dirty
|
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
|
|
|
_trackId = 0
|
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
|
|
|
_runnings = 0
|
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2024-01-13 15:53:06 +08:00
|
|
|
_shouldSchedule = false
|
2023-10-27 22:25:09 +08:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
|
|
|
_depsLength = 0
|
|
|
|
|
2021-06-25 05:44:32 +08:00
|
|
|
constructor(
|
|
|
|
public fn: () => T,
|
2023-10-27 22:25:09 +08:00
|
|
|
public trigger: () => void,
|
|
|
|
public scheduler?: EffectScheduler,
|
2022-01-28 18:35:09 +08:00
|
|
|
scope?: EffectScope,
|
2021-07-07 21:07:19 +08:00
|
|
|
) {
|
|
|
|
recordEffectScope(this, scope)
|
|
|
|
}
|
2020-04-15 05:31:35 +08:00
|
|
|
|
2023-10-27 22:25:09 +08:00
|
|
|
public get dirty() {
|
2024-02-06 18:44:09 +08:00
|
|
|
if (
|
|
|
|
this._dirtyLevel === DirtyLevels.MaybeDirty_ComputedSideEffect ||
|
|
|
|
this._dirtyLevel === DirtyLevels.MaybeDirty
|
|
|
|
) {
|
2024-02-06 18:23:56 +08:00
|
|
|
this._dirtyLevel = DirtyLevels.QueryingDirty
|
2023-10-27 22:25:09 +08:00
|
|
|
pauseTracking()
|
2024-01-13 15:53:06 +08:00
|
|
|
for (let i = 0; i < this._depsLength; i++) {
|
|
|
|
const dep = this.deps[i]
|
2023-10-27 22:25:09 +08:00
|
|
|
if (dep.computed) {
|
|
|
|
triggerComputed(dep.computed)
|
2024-01-13 15:53:06 +08:00
|
|
|
if (this._dirtyLevel >= DirtyLevels.Dirty) {
|
2023-10-27 22:25:09 +08:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-02-06 18:23:56 +08:00
|
|
|
if (this._dirtyLevel === DirtyLevels.QueryingDirty) {
|
2024-01-13 15:53:06 +08:00
|
|
|
this._dirtyLevel = DirtyLevels.NotDirty
|
|
|
|
}
|
2023-10-27 22:25:09 +08:00
|
|
|
resetTracking()
|
|
|
|
}
|
2024-01-13 15:53:06 +08:00
|
|
|
return this._dirtyLevel >= DirtyLevels.Dirty
|
2023-10-27 22:25:09 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
public set dirty(v) {
|
|
|
|
this._dirtyLevel = v ? DirtyLevels.Dirty : DirtyLevels.NotDirty
|
|
|
|
}
|
|
|
|
|
2021-06-25 05:44:32 +08:00
|
|
|
run() {
|
2023-10-27 22:25:09 +08:00
|
|
|
this._dirtyLevel = DirtyLevels.NotDirty
|
2021-06-25 05:44:32 +08:00
|
|
|
if (!this.active) {
|
|
|
|
return this.fn()
|
2020-04-03 07:49:45 +08:00
|
|
|
}
|
2022-01-28 18:35:09 +08:00
|
|
|
let lastShouldTrack = shouldTrack
|
2023-10-27 22:25:09 +08:00
|
|
|
let lastEffect = activeEffect
|
2022-01-28 18:35:09 +08:00
|
|
|
try {
|
|
|
|
shouldTrack = true
|
2023-10-27 22:25:09 +08:00
|
|
|
activeEffect = this
|
|
|
|
this._runnings++
|
|
|
|
preCleanupEffect(this)
|
2022-01-28 18:35:09 +08:00
|
|
|
return this.fn()
|
|
|
|
} finally {
|
2023-10-27 22:25:09 +08:00
|
|
|
postCleanupEffect(this)
|
|
|
|
this._runnings--
|
|
|
|
activeEffect = lastEffect
|
2022-01-28 18:35:09 +08:00
|
|
|
shouldTrack = lastShouldTrack
|
2020-04-03 07:49:45 +08:00
|
|
|
}
|
2021-06-25 05:44:32 +08:00
|
|
|
}
|
2018-11-14 00:03:35 +08:00
|
|
|
|
2021-06-25 05:44:32 +08:00
|
|
|
stop() {
|
2023-10-27 22:25:09 +08:00
|
|
|
if (this.active) {
|
|
|
|
preCleanupEffect(this)
|
|
|
|
postCleanupEffect(this)
|
|
|
|
this.onStop?.()
|
2021-06-25 05:44:32 +08:00
|
|
|
this.active = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-27 22:25:09 +08:00
|
|
|
function triggerComputed(computed: ComputedRefImpl<any>) {
|
|
|
|
return computed.value
|
|
|
|
}
|
|
|
|
|
|
|
|
function preCleanupEffect(effect: ReactiveEffect) {
|
|
|
|
effect._trackId++
|
|
|
|
effect._depsLength = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
function postCleanupEffect(effect: ReactiveEffect) {
|
2024-02-06 18:23:56 +08:00
|
|
|
if (effect.deps.length > effect._depsLength) {
|
2023-10-27 22:25:09 +08:00
|
|
|
for (let i = effect._depsLength; i < effect.deps.length; i++) {
|
|
|
|
cleanupDepEffect(effect.deps[i], effect)
|
|
|
|
}
|
|
|
|
effect.deps.length = effect._depsLength
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function cleanupDepEffect(dep: Dep, effect: ReactiveEffect) {
|
|
|
|
const trackId = dep.get(effect)
|
|
|
|
if (trackId !== undefined && effect._trackId !== trackId) {
|
|
|
|
dep.delete(effect)
|
|
|
|
if (dep.size === 0) {
|
|
|
|
dep.cleanup()
|
2021-07-08 00:33:37 +08:00
|
|
|
}
|
|
|
|
}
|
2021-07-08 02:13:23 +08:00
|
|
|
}
|
|
|
|
|
2021-07-09 05:09:23 +08:00
|
|
|
export interface DebuggerOptions {
|
|
|
|
onTrack?: (event: DebuggerEvent) => void
|
|
|
|
onTrigger?: (event: DebuggerEvent) => void
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface ReactiveEffectOptions extends DebuggerOptions {
|
2021-06-25 05:44:32 +08:00
|
|
|
lazy?: boolean
|
|
|
|
scheduler?: EffectScheduler
|
2021-07-07 21:07:19 +08:00
|
|
|
scope?: EffectScope
|
2021-06-25 05:44:32 +08:00
|
|
|
allowRecurse?: boolean
|
|
|
|
onStop?: () => void
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface ReactiveEffectRunner<T = any> {
|
|
|
|
(): T
|
|
|
|
effect: ReactiveEffect
|
|
|
|
}
|
|
|
|
|
2023-03-31 17:06:10 +08:00
|
|
|
/**
|
|
|
|
* Registers the given function to track reactive updates.
|
|
|
|
*
|
|
|
|
* The given function will be run once immediately. Every time any reactive
|
|
|
|
* property that's accessed within it gets updated, the function will run again.
|
|
|
|
*
|
|
|
|
* @param fn - The function that will track reactive updates.
|
|
|
|
* @param options - Allows to control the effect's behaviour.
|
|
|
|
* @returns A runner that can be used to control the effect after creation.
|
|
|
|
*/
|
2021-06-25 05:44:32 +08:00
|
|
|
export function effect<T = any>(
|
|
|
|
fn: () => T,
|
|
|
|
options?: ReactiveEffectOptions,
|
|
|
|
): ReactiveEffectRunner {
|
2023-09-05 16:27:54 +08:00
|
|
|
if ((fn as ReactiveEffectRunner).effect instanceof ReactiveEffect) {
|
2021-06-25 05:44:32 +08:00
|
|
|
fn = (fn as ReactiveEffectRunner).effect.fn
|
|
|
|
}
|
|
|
|
|
2023-10-27 22:25:09 +08:00
|
|
|
const _effect = new ReactiveEffect(fn, NOOP, () => {
|
|
|
|
if (_effect.dirty) {
|
|
|
|
_effect.run()
|
|
|
|
}
|
|
|
|
})
|
2021-06-25 05:44:32 +08:00
|
|
|
if (options) {
|
|
|
|
extend(_effect, options)
|
2021-07-07 21:07:19 +08:00
|
|
|
if (options.scope) recordEffectScope(_effect, options.scope)
|
2021-06-25 05:44:32 +08:00
|
|
|
}
|
|
|
|
if (!options || !options.lazy) {
|
|
|
|
_effect.run()
|
|
|
|
}
|
|
|
|
const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
|
|
|
|
runner.effect = _effect
|
|
|
|
return runner
|
|
|
|
}
|
|
|
|
|
2023-03-31 17:06:10 +08:00
|
|
|
/**
|
|
|
|
* Stops the effect associated with the given runner.
|
|
|
|
*
|
|
|
|
* @param runner - Association with the effect to stop tracking.
|
|
|
|
*/
|
2021-06-25 05:44:32 +08:00
|
|
|
export function stop(runner: ReactiveEffectRunner) {
|
|
|
|
runner.effect.stop()
|
2018-11-14 00:03:35 +08:00
|
|
|
}
|
|
|
|
|
2022-01-30 18:52:23 +08:00
|
|
|
export let shouldTrack = true
|
2023-10-27 22:25:09 +08:00
|
|
|
export let pauseScheduleStack = 0
|
|
|
|
|
2020-02-18 12:14:07 +08:00
|
|
|
const trackStack: boolean[] = []
|
2019-09-05 06:20:47 +08:00
|
|
|
|
2023-03-31 17:06:10 +08:00
|
|
|
/**
|
|
|
|
* Temporarily pauses tracking.
|
|
|
|
*/
|
2019-09-05 06:20:47 +08:00
|
|
|
export function pauseTracking() {
|
2020-02-18 12:14:07 +08:00
|
|
|
trackStack.push(shouldTrack)
|
2019-09-05 06:20:47 +08:00
|
|
|
shouldTrack = false
|
|
|
|
}
|
|
|
|
|
2023-03-31 17:06:10 +08:00
|
|
|
/**
|
|
|
|
* Re-enables effect tracking (if it was paused).
|
|
|
|
*/
|
2020-02-18 12:14:07 +08:00
|
|
|
export function enableTracking() {
|
|
|
|
trackStack.push(shouldTrack)
|
2019-09-05 06:20:47 +08:00
|
|
|
shouldTrack = true
|
|
|
|
}
|
|
|
|
|
2023-03-31 17:06:10 +08:00
|
|
|
/**
|
|
|
|
* Resets the previous global effect tracking state.
|
|
|
|
*/
|
2020-02-18 12:14:07 +08:00
|
|
|
export function resetTracking() {
|
|
|
|
const last = trackStack.pop()
|
|
|
|
shouldTrack = last === undefined ? true : last
|
|
|
|
}
|
|
|
|
|
2023-10-27 22:25:09 +08:00
|
|
|
export function pauseScheduling() {
|
|
|
|
pauseScheduleStack++
|
|
|
|
}
|
2021-06-24 05:22:21 +08:00
|
|
|
|
2023-10-27 22:25:09 +08:00
|
|
|
export function resetScheduling() {
|
|
|
|
pauseScheduleStack--
|
|
|
|
while (!pauseScheduleStack && queueEffectSchedulers.length) {
|
|
|
|
queueEffectSchedulers.shift()!()
|
2022-01-30 18:52:23 +08:00
|
|
|
}
|
2021-06-24 05:22:21 +08:00
|
|
|
}
|
|
|
|
|
2023-10-27 22:25:09 +08:00
|
|
|
export function trackEffect(
|
|
|
|
effect: ReactiveEffect,
|
2021-07-08 02:13:23 +08:00
|
|
|
dep: Dep,
|
2021-06-24 05:22:21 +08:00
|
|
|
debuggerEventExtraInfo?: DebuggerEventExtraInfo,
|
|
|
|
) {
|
2023-10-27 22:25:09 +08:00
|
|
|
if (dep.get(effect) !== effect._trackId) {
|
|
|
|
dep.set(effect, effect._trackId)
|
|
|
|
const oldDep = effect.deps[effect._depsLength]
|
|
|
|
if (oldDep !== dep) {
|
|
|
|
if (oldDep) {
|
|
|
|
cleanupDepEffect(oldDep, effect)
|
2021-06-24 05:20:38 +08:00
|
|
|
}
|
2023-10-27 22:25:09 +08:00
|
|
|
effect.deps[effect._depsLength++] = dep
|
|
|
|
} else {
|
|
|
|
effect._depsLength++
|
2021-06-24 05:20:38 +08:00
|
|
|
}
|
2021-07-08 12:31:26 +08:00
|
|
|
if (__DEV__) {
|
2023-10-27 22:25:09 +08:00
|
|
|
effect.onTrack?.(extend({ effect }, debuggerEventExtraInfo!))
|
2021-07-08 12:31:26 +08:00
|
|
|
}
|
2021-06-24 05:22:21 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-09 15:27:17 +08:00
|
|
|
const queueEffectSchedulers: EffectScheduler[] = []
|
2022-04-15 16:43:17 +08:00
|
|
|
|
2023-10-27 22:25:09 +08:00
|
|
|
export function triggerEffects(
|
|
|
|
dep: Dep,
|
|
|
|
dirtyLevel: DirtyLevels,
|
2022-04-15 16:43:17 +08:00
|
|
|
debuggerEventExtraInfo?: DebuggerEventExtraInfo,
|
|
|
|
) {
|
2023-10-27 22:25:09 +08:00
|
|
|
pauseScheduling()
|
|
|
|
for (const effect of dep.keys()) {
|
2024-02-06 18:23:56 +08:00
|
|
|
// dep.get(effect) is very expensive, we need to calculate it lazily and reuse the result
|
|
|
|
let tracking: boolean | undefined
|
2024-01-15 23:38:57 +08:00
|
|
|
if (
|
|
|
|
effect._dirtyLevel < dirtyLevel &&
|
2024-02-06 18:23:56 +08:00
|
|
|
(tracking ??= dep.get(effect) === effect._trackId)
|
2024-01-15 23:38:57 +08:00
|
|
|
) {
|
2024-02-06 18:23:56 +08:00
|
|
|
effect._shouldSchedule ||= effect._dirtyLevel === DirtyLevels.NotDirty
|
2023-10-27 22:25:09 +08:00
|
|
|
effect._dirtyLevel = dirtyLevel
|
2021-06-24 05:22:21 +08:00
|
|
|
}
|
2024-01-13 15:53:06 +08:00
|
|
|
if (
|
|
|
|
effect._shouldSchedule &&
|
2024-02-06 18:23:56 +08:00
|
|
|
(tracking ??= dep.get(effect) === effect._trackId)
|
2024-01-13 15:53:06 +08:00
|
|
|
) {
|
2024-02-06 18:23:56 +08:00
|
|
|
if (__DEV__) {
|
|
|
|
effect.onTrigger?.(extend({ effect }, debuggerEventExtraInfo))
|
|
|
|
}
|
|
|
|
effect.trigger()
|
2024-02-06 18:44:09 +08:00
|
|
|
if (
|
|
|
|
(!effect._runnings || effect.allowRecurse) &&
|
|
|
|
effect._dirtyLevel !== DirtyLevels.MaybeDirty_ComputedSideEffect
|
|
|
|
) {
|
2024-02-06 18:23:56 +08:00
|
|
|
effect._shouldSchedule = false
|
|
|
|
if (effect.scheduler) {
|
|
|
|
queueEffectSchedulers.push(effect.scheduler)
|
|
|
|
}
|
|
|
|
}
|
2024-01-13 15:53:06 +08:00
|
|
|
}
|
2021-06-24 05:20:38 +08:00
|
|
|
}
|
2024-02-06 18:23:56 +08:00
|
|
|
resetScheduling()
|
2023-02-01 16:20:47 +08:00
|
|
|
}
|