export type DebuggerEvent = {
effect: ReactiveEffect
+} & DebuggerEventExtraInfo
+
+export type DebuggerEventExtraInfo = {
target: object
type: TrackOpTypes | TriggerOpTypes
key: any
-} & DebuggerEventExtraInfo
-
-export interface DebuggerEventExtraInfo {
newValue?: any
oldValue?: any
oldTarget?: Map<any, any> | Set<any>
} finally {
effectStack.pop()
resetTracking()
- activeEffect = effectStack[effectStack.length - 1]
+ const n = effectStack.length
+ activeEffect = n > 0 ? effectStack[n - 1] : undefined
}
}
} as ReactiveEffect
}
export function track(target: object, type: TrackOpTypes, key: unknown) {
- if (!shouldTrack || activeEffect === undefined) {
+ if (!isTracking()) {
return
}
let depsMap = targetMap.get(target)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
- if (!dep.has(activeEffect)) {
- dep.add(activeEffect)
- activeEffect.deps.push(dep)
- if (__DEV__ && activeEffect.options.onTrack) {
- activeEffect.options.onTrack({
- effect: activeEffect,
- target,
- type,
- key
- })
+
+ const eventInfo = __DEV__
+ ? { effect: activeEffect, target, type, key }
+ : undefined
+
+ trackEffects(dep, eventInfo)
+}
+
+export function isTracking() {
+ return shouldTrack && activeEffect !== undefined
+}
+
+export function trackEffects(
+ dep: Set<ReactiveEffect>,
+ debuggerEventExtraInfo?: DebuggerEventExtraInfo
+) {
+ if (!dep.has(activeEffect!)) {
+ dep.add(activeEffect!)
+ activeEffect!.deps.push(dep)
+ if (__DEV__ && activeEffect!.options.onTrack) {
+ activeEffect!.options.onTrack(
+ Object.assign(
+ {
+ effect: activeEffect!
+ },
+ debuggerEventExtraInfo
+ )
+ )
}
}
}
return
}
- const effects = new Set<ReactiveEffect>()
- const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
- if (effectsToAdd) {
- effectsToAdd.forEach(effect => {
- if (effect !== activeEffect || effect.allowRecurse) {
- effects.add(effect)
- }
- })
- }
- }
-
+ let sets: DepSets = []
if (type === TriggerOpTypes.CLEAR) {
// collection being cleared
// trigger all effects for target
- depsMap.forEach(add)
+ sets = [...depsMap.values()]
} else if (key === 'length' && isArray(target)) {
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= (newValue as number)) {
- add(dep)
+ sets.push(dep)
}
})
} else {
// schedule runs for SET | ADD | DELETE
if (key !== void 0) {
- add(depsMap.get(key))
+ sets.push(depsMap.get(key))
}
// also run for iteration key on ADD | DELETE | Map.SET
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
- add(depsMap.get(ITERATE_KEY))
+ sets.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
- add(depsMap.get(MAP_KEY_ITERATE_KEY))
+ sets.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isIntegerKey(key)) {
// new index added to array -> length changes
- add(depsMap.get('length'))
+ sets.push(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
if (!isArray(target)) {
- add(depsMap.get(ITERATE_KEY))
+ sets.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
- add(depsMap.get(MAP_KEY_ITERATE_KEY))
+ sets.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET:
if (isMap(target)) {
- add(depsMap.get(ITERATE_KEY))
+ sets.push(depsMap.get(ITERATE_KEY))
}
break
}
}
+ const eventInfo = __DEV__
+ ? { target, type, key, newValue, oldValue, oldTarget }
+ : undefined
+ triggerMultiEffects(sets, eventInfo)
+}
+
+type DepSets = (Dep | undefined)[]
+
+export function triggerMultiEffects(
+ depSets: DepSets,
+ debuggerEventExtraInfo?: DebuggerEventExtraInfo
+) {
+ if (depSets.length === 1) {
+ if (depSets[0]) {
+ triggerEffects(depSets[0], debuggerEventExtraInfo)
+ }
+ } else {
+ const sets = depSets.filter(s => !!s) as Dep[]
+ triggerEffects(concatSets(sets), debuggerEventExtraInfo)
+ }
+}
+
+function concatSets<T>(sets: Set<T>[]): Set<T> {
+ const all = ([] as T[]).concat(...sets.map(s => [...s!]))
+ return new Set(all)
+}
+
+export function triggerEffects(
+ dep: Dep,
+ debuggerEventExtraInfo?: DebuggerEventExtraInfo
+) {
const run = (effect: ReactiveEffect) => {
if (__DEV__ && effect.options.onTrigger) {
- effect.options.onTrigger({
- effect,
- target,
- key,
- type,
- newValue,
- oldValue,
- oldTarget
- })
+ effect.options.onTrigger(
+ Object.assign({ effect }, debuggerEventExtraInfo)
+ )
}
if (effect.options.scheduler) {
effect.options.scheduler(effect)
}
}
- effects.forEach(run)
+ const immutableDeps = [...dep]
+ immutableDeps.forEach(effect => {
+ if (effect !== activeEffect || effect.allowRecurse) {
+ run(effect)
+ }
+ })
}
-import { track, trigger } from './effect'
+import {
+ isTracking,
+ ReactiveEffect,
+ trackEffects,
+ triggerEffects
+} from './effect'
import { TrackOpTypes, TriggerOpTypes } from './operations'
import { isArray, isObject, hasChanged } from '@vue/shared'
import { reactive, isProxy, toRaw, isReactive } from './reactive'
* @internal
*/
_shallow?: boolean
+
+ /**
+ * Deps are maintained locally rather than in depsMap for performance reasons.
+ */
+ dep?: Set<ReactiveEffect>
+}
+
+type RefBase<T> = {
+ dep?: Set<ReactiveEffect>
+ value: T
+}
+
+export function trackRefValue(ref: RefBase<any>) {
+ if (isTracking()) {
+ ref = toRaw(ref)
+ const eventInfo = __DEV__
+ ? { target: ref, type: TrackOpTypes.GET, key: 'value' }
+ : undefined
+ if (!ref.dep) {
+ ref.dep = new Set<ReactiveEffect>()
+ }
+ trackEffects(ref.dep, eventInfo)
+ }
+}
+
+export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
+ ref = toRaw(ref)
+ if (ref.dep) {
+ const eventInfo = __DEV__
+ ? {
+ target: ref,
+ type: TriggerOpTypes.SET,
+ key: 'value',
+ newValue: newVal
+ }
+ : undefined
+ triggerEffects(ref.dep, eventInfo)
+ }
}
export type ToRef<T> = [T] extends [Ref] ? T : Ref<UnwrapRef<T>>
}
class RefImpl<T> {
- private _rawValue: T
-
private _value: T
+ private _rawValue: T
+ public dep?: Set<ReactiveEffect> = undefined
public readonly __v_isRef = true
constructor(value: T, public readonly _shallow = false) {
}
get value() {
- track(toRaw(this), TrackOpTypes.GET, 'value')
+ trackRefValue(this)
return this._value
}
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = this._shallow ? newVal : convert(newVal)
- trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
+ triggerRefValue(this, newVal)
}
}
}
}
export function triggerRef(ref: Ref) {
- trigger(toRaw(ref), TriggerOpTypes.SET, 'value', __DEV__ ? ref.value : void 0)
+ triggerRefValue(ref, __DEV__ ? ref.value : void 0)
}
export function unref<T>(ref: T | Ref<T>): T {
}
class CustomRefImpl<T> {
+ public dep?: Set<ReactiveEffect> = undefined
+
private readonly _get: ReturnType<CustomRefFactory<T>>['get']
private readonly _set: ReturnType<CustomRefFactory<T>>['set']
constructor(factory: CustomRefFactory<T>) {
const { get, set } = factory(
- () => track(this, TrackOpTypes.GET, 'value'),
- () => trigger(this, TriggerOpTypes.SET, 'value')
+ () => trackRefValue(this),
+ () => triggerRefValue(this)
)
this._get = get
this._set = set