diff --git a/packages/reactivity/__tests__/effect.spec.ts b/packages/reactivity/__tests__/effect.spec.ts index 7422b4000ab..5cb17ef40f1 100644 --- a/packages/reactivity/__tests__/effect.spec.ts +++ b/packages/reactivity/__tests__/effect.spec.ts @@ -11,12 +11,8 @@ import { readonly, ReactiveEffectRunner } from '../src/index' -import { - ITERATE_KEY, - getDepFromReactive, - pauseScheduling, - resetScheduling -} from '../src/effect' +import { pauseScheduling, resetScheduling } from '../src/effect' +import { ITERATE_KEY, getDepFromReactive } from '../src/reactiveEffect' describe('reactivity/effect', () => { it('should run the passed function once (wrapped by a effect)', () => { diff --git a/packages/reactivity/src/baseHandlers.ts b/packages/reactivity/src/baseHandlers.ts index 3f7253c3dc8..36e4d311b4b 100644 --- a/packages/reactivity/src/baseHandlers.ts +++ b/packages/reactivity/src/baseHandlers.ts @@ -12,14 +12,12 @@ import { } from './reactive' import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants' import { - track, - trigger, - ITERATE_KEY, pauseTracking, resetTracking, pauseScheduling, resetScheduling } from './effect' +import { track, trigger, ITERATE_KEY } from './reactiveEffect' import { isObject, hasOwn, diff --git a/packages/reactivity/src/collectionHandlers.ts b/packages/reactivity/src/collectionHandlers.ts index a9500b19bdb..e8d99840f71 100644 --- a/packages/reactivity/src/collectionHandlers.ts +++ b/packages/reactivity/src/collectionHandlers.ts @@ -1,5 +1,10 @@ import { toRaw, toReactive, toReadonly } from './reactive' -import { track, trigger, ITERATE_KEY, MAP_KEY_ITERATE_KEY } from './effect' +import { + track, + trigger, + ITERATE_KEY, + MAP_KEY_ITERATE_KEY +} from './reactiveEffect' import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants' import { capitalize, hasOwn, hasChanged, toRawType, isMap } from '@vue/shared' diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 3140c4be251..e3d762f2005 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -1,15 +1,8 @@ +import { extend } from '@vue/shared' +import type { ComputedRefImpl } from './computed' import { DirtyLevels, TrackOpTypes, TriggerOpTypes } from './constants' -import { extend, isArray, isIntegerKey, isMap } from '@vue/shared' +import type { Dep } from './dep' import { EffectScope, recordEffectScope } from './effectScope' -import { createDep, Dep } from './dep' -import type { ComputedRefImpl } from './computed' - -// The main WeakMap that stores {target -> key -> dep} connections. -// Conceptually, it's easier to think of a dependency as a Dep class -// which maintains a Set of subscribers, but we simply store them as -// raw Sets to reduce memory overhead. -type KeyToDepMap = Map -const targetMap = new WeakMap() export type EffectScheduler = ( onScheduled: (cb: () => void) => void, @@ -31,9 +24,6 @@ export type DebuggerEventExtraInfo = { export let activeEffect: ReactiveEffect | undefined -export const ITERATE_KEY = Symbol(__DEV__ ? 'iterate' : '') -export const MAP_KEY_ITERATE_KEY = Symbol(__DEV__ ? 'Map key iterate' : '') - export class ReactiveEffect { active = true deps: Dep[] = [] @@ -260,38 +250,6 @@ export function resetScheduling() { } } -/** - * Tracks access to a reactive property. - * - * This will check which effect is running at the moment and record it as dep - * which records all effects that depend on the reactive property. - * - * @param target - Object holding the reactive property. - * @param type - Defines the type of access to the reactive property. - * @param key - Identifier of the reactive property to track. - */ -export function track(target: object, type: TrackOpTypes, key: unknown) { - if (shouldTrack && activeEffect) { - let depsMap = targetMap.get(target) - if (!depsMap) { - targetMap.set(target, (depsMap = new Map())) - } - let dep = depsMap.get(key) - if (!dep) { - depsMap.set(key, (dep = createDep(() => depsMap!.delete(key)))) - } - if (__DEV__) { - trackEffect(activeEffect, dep, { - target, - type, - key - }) - } else { - trackEffect(activeEffect, dep) - } - } -} - export function trackEffect( effect: ReactiveEffect, dep: Dep, @@ -314,95 +272,6 @@ export function trackEffect( } } -/** - * Finds all deps associated with the target (or a specific property) and - * triggers the effects stored within. - * - * @param target - The reactive object. - * @param type - Defines the type of the operation that needs to trigger effects. - * @param key - Can be used to target a specific reactive property in the target object. - */ -export function trigger( - target: object, - type: TriggerOpTypes, - key?: unknown, - newValue?: unknown, - oldValue?: unknown, - oldTarget?: Map | Set -) { - const depsMap = targetMap.get(target) - if (!depsMap) { - // never been tracked - return - } - - let deps: (Dep | undefined)[] = [] - if (type === TriggerOpTypes.CLEAR) { - // collection being cleared - // trigger all effects for target - deps = [...depsMap.values()] - } else if (key === 'length' && isArray(target)) { - const newLength = Number(newValue) - depsMap.forEach((dep, key) => { - if (key === 'length' || key >= newLength) { - deps.push(dep) - } - }) - } else { - // schedule runs for SET | ADD | DELETE - if (key !== void 0) { - deps.push(depsMap.get(key)) - } - - // also run for iteration key on ADD | DELETE | Map.SET - switch (type) { - case TriggerOpTypes.ADD: - if (!isArray(target)) { - deps.push(depsMap.get(ITERATE_KEY)) - if (isMap(target)) { - deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)) - } - } else if (isIntegerKey(key)) { - // new index added to array -> length changes - deps.push(depsMap.get('length')) - } - break - case TriggerOpTypes.DELETE: - if (!isArray(target)) { - deps.push(depsMap.get(ITERATE_KEY)) - if (isMap(target)) { - deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)) - } - } - break - case TriggerOpTypes.SET: - if (isMap(target)) { - deps.push(depsMap.get(ITERATE_KEY)) - } - break - } - } - - pauseScheduling() - for (const dep of deps) { - if (dep) { - if (__DEV__) { - triggerEffects(dep, DirtyLevels.Dirty, { - target, - type, - key, - newValue, - oldValue, - oldTarget - }) - } else { - triggerEffects(dep, DirtyLevels.Dirty) - } - } - } - resetScheduling() -} - const queueEffectCbs: (() => void)[] = [] const pushEffectCb = queueEffectCbs.push.bind(queueEffectCbs) @@ -436,7 +305,3 @@ export function triggerEffects( } resetScheduling() } - -export function getDepFromReactive(object: any, key: string | number | symbol) { - return targetMap.get(object)?.get(key) -} diff --git a/packages/reactivity/src/index.ts b/packages/reactivity/src/index.ts index a2059c25604..9497527e81e 100644 --- a/packages/reactivity/src/index.ts +++ b/packages/reactivity/src/index.ts @@ -48,14 +48,11 @@ export { deferredComputed } from './deferredComputed' export { effect, stop, - trigger, - track, enableTracking, pauseTracking, resetTracking, pauseScheduling, resetScheduling, - ITERATE_KEY, ReactiveEffect, type ReactiveEffectRunner, type ReactiveEffectOptions, @@ -64,6 +61,7 @@ export { type DebuggerEvent, type DebuggerEventExtraInfo } from './effect' +export { trigger, track, ITERATE_KEY } from './reactiveEffect' export { effectScope, EffectScope, diff --git a/packages/reactivity/src/reactiveEffect.ts b/packages/reactivity/src/reactiveEffect.ts new file mode 100644 index 00000000000..8092ebb21f6 --- /dev/null +++ b/packages/reactivity/src/reactiveEffect.ts @@ -0,0 +1,146 @@ +import { isArray, isIntegerKey, isMap } from '@vue/shared' +import { DirtyLevels, TrackOpTypes, TriggerOpTypes } from './constants' +import { createDep, Dep } from './dep' +import { + activeEffect, + pauseScheduling, + resetScheduling, + shouldTrack, + trackEffect, + triggerEffects +} from './effect' + +// The main WeakMap that stores {target -> key -> dep} connections. +// Conceptually, it's easier to think of a dependency as a Dep class +// which maintains a Set of subscribers, but we simply store them as +// raw Sets to reduce memory overhead. +type KeyToDepMap = Map +const targetMap = new WeakMap() + +export const ITERATE_KEY = Symbol(__DEV__ ? 'iterate' : '') +export const MAP_KEY_ITERATE_KEY = Symbol(__DEV__ ? 'Map key iterate' : '') + +/** + * Tracks access to a reactive property. + * + * This will check which effect is running at the moment and record it as dep + * which records all effects that depend on the reactive property. + * + * @param target - Object holding the reactive property. + * @param type - Defines the type of access to the reactive property. + * @param key - Identifier of the reactive property to track. + */ +export function track(target: object, type: TrackOpTypes, key: unknown) { + if (shouldTrack && activeEffect) { + let depsMap = targetMap.get(target) + if (!depsMap) { + targetMap.set(target, (depsMap = new Map())) + } + let dep = depsMap.get(key) + if (!dep) { + depsMap.set(key, (dep = createDep(() => depsMap!.delete(key)))) + } + if (__DEV__) { + trackEffect(activeEffect, dep, { + target, + type, + key + }) + } else { + trackEffect(activeEffect, dep) + } + } +} + +/** + * Finds all deps associated with the target (or a specific property) and + * triggers the effects stored within. + * + * @param target - The reactive object. + * @param type - Defines the type of the operation that needs to trigger effects. + * @param key - Can be used to target a specific reactive property in the target object. + */ +export function trigger( + target: object, + type: TriggerOpTypes, + key?: unknown, + newValue?: unknown, + oldValue?: unknown, + oldTarget?: Map | Set +) { + const depsMap = targetMap.get(target) + if (!depsMap) { + // never been tracked + return + } + + let deps: (Dep | undefined)[] = [] + if (type === TriggerOpTypes.CLEAR) { + // collection being cleared + // trigger all effects for target + deps = [...depsMap.values()] + } else if (key === 'length' && isArray(target)) { + const newLength = Number(newValue) + depsMap.forEach((dep, key) => { + if (key === 'length' || key >= newLength) { + deps.push(dep) + } + }) + } else { + // schedule runs for SET | ADD | DELETE + if (key !== void 0) { + deps.push(depsMap.get(key)) + } + + // also run for iteration key on ADD | DELETE | Map.SET + switch (type) { + case TriggerOpTypes.ADD: + if (!isArray(target)) { + deps.push(depsMap.get(ITERATE_KEY)) + if (isMap(target)) { + deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)) + } + } else if (isIntegerKey(key)) { + // new index added to array -> length changes + deps.push(depsMap.get('length')) + } + break + case TriggerOpTypes.DELETE: + if (!isArray(target)) { + deps.push(depsMap.get(ITERATE_KEY)) + if (isMap(target)) { + deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)) + } + } + break + case TriggerOpTypes.SET: + if (isMap(target)) { + deps.push(depsMap.get(ITERATE_KEY)) + } + break + } + } + + pauseScheduling() + for (const dep of deps) { + if (dep) { + if (__DEV__) { + triggerEffects(dep, DirtyLevels.Dirty, { + target, + type, + key, + newValue, + oldValue, + oldTarget + }) + } else { + triggerEffects(dep, DirtyLevels.Dirty) + } + } + } + resetScheduling() +} + +export function getDepFromReactive(object: any, key: string | number | symbol) { + return targetMap.get(object)?.get(key) +} diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index d0a2fdf8e78..90c564ed8b5 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -1,6 +1,5 @@ import { activeEffect, - getDepFromReactive, shouldTrack, trackEffect, triggerEffects @@ -19,6 +18,7 @@ import type { ShallowReactiveMarker } from './reactive' import { CollectionTypes } from './collectionHandlers' import { createDep, Dep } from './dep' import { ComputedRefImpl } from './computed' +import { getDepFromReactive } from './reactiveEffect' declare const RefSymbol: unique symbol export declare const RawSymbol: unique symbol