From f485ea01a9d1d7aca3bdc61b12f768de91c8d583 Mon Sep 17 00:00:00 2001 From: johnsoncodehk Date: Fri, 13 May 2022 19:01:02 +0800 Subject: [PATCH 001/192] perf: calculate embedded `computed()` on-demand --- .../reactivity/__tests__/computed.spec.ts | 51 +++++++++++++++++++ packages/reactivity/src/baseHandlers.ts | 4 +- packages/reactivity/src/collectionHandlers.ts | 8 +-- packages/reactivity/src/computed.ts | 25 +++++++-- packages/reactivity/src/deferredComputed.ts | 6 +-- packages/reactivity/src/effect.ts | 24 ++++----- packages/reactivity/src/ref.ts | 11 ++-- packages/runtime-core/src/compat/global.ts | 2 +- packages/runtime-core/src/componentProps.ts | 2 +- 9 files changed, 99 insertions(+), 34 deletions(-) diff --git a/packages/reactivity/__tests__/computed.spec.ts b/packages/reactivity/__tests__/computed.spec.ts index 51157944355..9169d75207c 100644 --- a/packages/reactivity/__tests__/computed.spec.ts +++ b/packages/reactivity/__tests__/computed.spec.ts @@ -290,4 +290,55 @@ describe('reactivity/computed', () => { oldValue: 2 }) }) + + it('chained computed value on-demand trigger', () => { + const c1Spy = jest.fn() + const c2Spy = jest.fn() + + const src = ref(0) + const c1 = computed(() => { + c1Spy() + return src.value < 5 + }) + const c2 = computed(() => { + c2Spy() + return c1.value ? '< 5' : '>= 5' + }) + + expect(c1Spy).toHaveBeenCalledTimes(0) + expect(c2Spy).toHaveBeenCalledTimes(0) + + expect(src.value).toBe(0) + expect(c2.value).toBe('< 5') + expect(c1Spy).toHaveBeenCalledTimes(1) + expect(c2Spy).toHaveBeenCalledTimes(1) + + src.value++ + expect(c2.value).toBe('< 5') + expect(c1Spy).toHaveBeenCalledTimes(2) + expect(c2Spy).toHaveBeenCalledTimes(1) + + for (let i = 0; i < 10; i++) { + src.value++ + } + expect(src.value).toBe(11) + expect(c2.value).toBe('>= 5') + expect(c1Spy).toHaveBeenCalledTimes(3) + expect(c2Spy).toHaveBeenCalledTimes(2) + + src.value++ + expect(src.value).toBe(12) + expect(c2.value).toBe('>= 5') + expect(c1Spy).toHaveBeenCalledTimes(4) + expect(c2Spy).toHaveBeenCalledTimes(2) + + for (let i = 0; i < 100; i++) { + src.value++ + c2.value + } + expect(src.value).toBe(112) + expect(c2.value).toBe('>= 5') + expect(c1Spy).toHaveBeenCalledTimes(104) + expect(c2Spy).toHaveBeenCalledTimes(2) + }) }) diff --git a/packages/reactivity/src/baseHandlers.ts b/packages/reactivity/src/baseHandlers.ts index 45ecfa6d38a..43bd425d2a7 100644 --- a/packages/reactivity/src/baseHandlers.ts +++ b/packages/reactivity/src/baseHandlers.ts @@ -190,9 +190,9 @@ function createSetter(shallow = false) { // don't trigger if target is something up in the prototype chain of original if (target === toRaw(receiver)) { if (!hadKey) { - trigger(target, TriggerOpTypes.ADD, key, value) + trigger(target, TriggerOpTypes.ADD, undefined, key, value) } else if (hasChanged(value, oldValue)) { - trigger(target, TriggerOpTypes.SET, key, value, oldValue) + trigger(target, TriggerOpTypes.SET, undefined, key, value, oldValue) } } return result diff --git a/packages/reactivity/src/collectionHandlers.ts b/packages/reactivity/src/collectionHandlers.ts index 381bbad6c28..651cb1e289f 100644 --- a/packages/reactivity/src/collectionHandlers.ts +++ b/packages/reactivity/src/collectionHandlers.ts @@ -73,7 +73,7 @@ function add(this: SetTypes, value: unknown) { const hadKey = proto.has.call(target, value) if (!hadKey) { target.add(value) - trigger(target, TriggerOpTypes.ADD, value, value) + trigger(target, TriggerOpTypes.ADD, undefined, value, value) } return this } @@ -94,9 +94,9 @@ function set(this: MapTypes, key: unknown, value: unknown) { const oldValue = get.call(target, key) target.set(key, value) if (!hadKey) { - trigger(target, TriggerOpTypes.ADD, key, value) + trigger(target, TriggerOpTypes.ADD, undefined, key, value) } else if (hasChanged(value, oldValue)) { - trigger(target, TriggerOpTypes.SET, key, value, oldValue) + trigger(target, TriggerOpTypes.SET, undefined, key, value, oldValue) } return this } @@ -116,7 +116,7 @@ function deleteEntry(this: CollectionTypes, key: unknown) { // forward the operation before queueing reactions const result = target.delete(key) if (hadKey) { - trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue) + trigger(target, TriggerOpTypes.DELETE, undefined, key, undefined, oldValue) } return result } diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index b24484c9e62..1d177afbcff 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -33,6 +33,7 @@ export class ComputedRefImpl { public readonly [ReactiveFlags.IS_READONLY]: boolean = false public _dirty = true + public _computedsToAskDirty: ComputedRefImpl[] = [] public _cacheable: boolean constructor( @@ -41,10 +42,13 @@ export class ComputedRefImpl { isReadonly: boolean, isSSR: boolean ) { - this.effect = new ReactiveEffect(getter, () => { - if (!this._dirty) { + this.effect = new ReactiveEffect(getter, (_c) => { + if (_c) { + this._computedsToAskDirty.push(_c) + } + else if (!this._dirty) { this._dirty = true - triggerRefValue(this) + triggerRefValue(this, this) } }) this.effect.computed = this @@ -55,11 +59,24 @@ export class ComputedRefImpl { get value() { // the computed ref may get wrapped by other proxies e.g. readonly() #3376 const self = toRaw(this) + if (!self._dirty) { + for (const computedToAskDirty of self._computedsToAskDirty) { + computedToAskDirty.value + if (self._dirty) { + break + } + } + } trackRefValue(self) if (self._dirty || !self._cacheable) { + const newValue = self.effect.run()! + if (self._value !== newValue) { + triggerRefValue(this, undefined) + } + self._value = newValue self._dirty = false - self._value = self.effect.run()! } + self._computedsToAskDirty.length = 0 return self._value } diff --git a/packages/reactivity/src/deferredComputed.ts b/packages/reactivity/src/deferredComputed.ts index a23122046a4..07b4a496bf4 100644 --- a/packages/reactivity/src/deferredComputed.ts +++ b/packages/reactivity/src/deferredComputed.ts @@ -38,7 +38,7 @@ class DeferredComputedRefImpl { let compareTarget: any let hasCompareTarget = false let scheduled = false - this.effect = new ReactiveEffect(getter, (computedTrigger?: boolean) => { + this.effect = new ReactiveEffect(getter, (_c, computedTrigger?: boolean) => { if (this.dep) { if (computedTrigger) { compareTarget = this._value @@ -49,7 +49,7 @@ class DeferredComputedRefImpl { hasCompareTarget = false scheduler(() => { if (this.effect.active && this._get() !== valueToCompare) { - triggerRefValue(this) + triggerRefValue(this, undefined) } scheduled = false }) @@ -59,7 +59,7 @@ class DeferredComputedRefImpl { // deferred to be triggered in scheduler. for (const e of this.dep) { if (e.computed instanceof DeferredComputedRefImpl) { - e.scheduler!(true /* computedTrigger */) + e.scheduler!(undefined, true /* computedTrigger */) } } } diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index d4a34edfef4..4cfb9ec8dda 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -30,7 +30,7 @@ export let trackOpBit = 1 */ const maxMarkerBits = 30 -export type EffectScheduler = (...args: any[]) => any +export type EffectScheduler = (computedToAskDirty: ComputedRefImpl | undefined, ...args: any[]) => any export type DebuggerEvent = { effect: ReactiveEffect @@ -305,6 +305,7 @@ export function trackEffects( export function trigger( target: object, type: TriggerOpTypes, + computedToAskDirty: ComputedRefImpl | undefined, key?: unknown, newValue?: unknown, oldValue?: unknown, @@ -370,9 +371,9 @@ export function trigger( if (deps.length === 1) { if (deps[0]) { if (__DEV__) { - triggerEffects(deps[0], eventInfo) + triggerEffects(deps[0], computedToAskDirty, eventInfo) } else { - triggerEffects(deps[0]) + triggerEffects(deps[0], computedToAskDirty) } } } else { @@ -383,33 +384,28 @@ export function trigger( } } if (__DEV__) { - triggerEffects(createDep(effects), eventInfo) + triggerEffects(createDep(effects), computedToAskDirty, eventInfo) } else { - triggerEffects(createDep(effects)) + triggerEffects(createDep(effects), computedToAskDirty) } } } export function triggerEffects( dep: Dep | ReactiveEffect[], + computedToAskDirty: ComputedRefImpl | undefined, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { // spread into array for stabilization const effects = isArray(dep) ? dep : [...dep] for (const effect of effects) { - if (effect.computed) { - triggerEffect(effect, debuggerEventExtraInfo) - } - } - for (const effect of effects) { - if (!effect.computed) { - triggerEffect(effect, debuggerEventExtraInfo) - } + triggerEffect(effect, computedToAskDirty, debuggerEventExtraInfo) } } function triggerEffect( effect: ReactiveEffect, + computedToAskDirty: ComputedRefImpl | undefined, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { if (effect !== activeEffect || effect.allowRecurse) { @@ -417,7 +413,7 @@ function triggerEffect( effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) } if (effect.scheduler) { - effect.scheduler() + effect.scheduler(computedToAskDirty) } else { effect.run() } diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 5dd31a9f8ca..b131ce58c58 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -18,6 +18,7 @@ import { import type { ShallowReactiveMarker } from './reactive' import { CollectionTypes } from './collectionHandlers' import { createDep, Dep } from './dep' +import { ComputedRefImpl } from './computed' declare const RefSymbol: unique symbol export declare const RawSymbol: unique symbol @@ -52,19 +53,19 @@ export function trackRefValue(ref: RefBase) { } } -export function triggerRefValue(ref: RefBase, newVal?: any) { +export function triggerRefValue(ref: RefBase, computedToAskDirty: ComputedRefImpl | undefined, newVal?: any) { ref = toRaw(ref) const dep = ref.dep if (dep) { if (__DEV__) { - triggerEffects(dep, { + triggerEffects(dep, computedToAskDirty, { target: ref, type: TriggerOpTypes.SET, key: 'value', newValue: newVal }) } else { - triggerEffects(dep) + triggerEffects(dep, computedToAskDirty) } } } @@ -155,7 +156,7 @@ class RefImpl { if (hasChanged(newVal, this._rawValue)) { this._rawValue = newVal this._value = useDirectValue ? newVal : toReactive(newVal) - triggerRefValue(this, newVal) + triggerRefValue(this, undefined, newVal) } } } @@ -282,7 +283,7 @@ class CustomRefImpl { constructor(factory: CustomRefFactory) { const { get, set } = factory( () => trackRefValue(this), - () => triggerRefValue(this) + () => triggerRefValue(this, undefined) ) this._get = get this._set = set diff --git a/packages/runtime-core/src/compat/global.ts b/packages/runtime-core/src/compat/global.ts index 9f1a6d1cd12..1cd9e886984 100644 --- a/packages/runtime-core/src/compat/global.ts +++ b/packages/runtime-core/src/compat/global.ts @@ -648,7 +648,7 @@ function defineReactiveSimple(obj: any, key: string, val: any) { }, set(newVal) { val = isObject(newVal) ? reactive(newVal) : newVal - trigger(obj, TriggerOpTypes.SET, key, newVal) + trigger(obj, TriggerOpTypes.SET, undefined, key, newVal) } }) } diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index 186384d5b82..b1179647871 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -333,7 +333,7 @@ export function updateProps( // trigger updates for $attrs in case it's used in component slots if (hasAttrsChanged) { - trigger(instance, TriggerOpTypes.SET, '$attrs') + trigger(instance, TriggerOpTypes.SET, undefined, '$attrs') } if (__DEV__) { From 208e59c9d9e44765b2bd6eccbf7f6f395ae18979 Mon Sep 17 00:00:00 2001 From: johnsoncodehk Date: Fri, 13 May 2022 19:38:25 +0800 Subject: [PATCH 002/192] fix: computed effect dons't transfer --- packages/reactivity/src/computed.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 1d177afbcff..e7bd253ea49 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -45,6 +45,7 @@ export class ComputedRefImpl { this.effect = new ReactiveEffect(getter, (_c) => { if (_c) { this._computedsToAskDirty.push(_c) + triggerRefValue(this, this) } else if (!this._dirty) { this._dirty = true From 4a3e554d56a0e35c709801a087aadaaf956cd380 Mon Sep 17 00:00:00 2001 From: johnsoncodehk Date: Fri, 13 May 2022 21:11:51 +0800 Subject: [PATCH 003/192] fix: revert effects trigger order --- packages/reactivity/src/effect.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 4cfb9ec8dda..14c2339478c 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -399,7 +399,14 @@ export function triggerEffects( // spread into array for stabilization const effects = isArray(dep) ? dep : [...dep] for (const effect of effects) { - triggerEffect(effect, computedToAskDirty, debuggerEventExtraInfo) + if (effect.computed) { + triggerEffect(effect, computedToAskDirty, debuggerEventExtraInfo) + } + } + for (const effect of effects) { + if (!effect.computed) { + triggerEffect(effect, computedToAskDirty, debuggerEventExtraInfo) + } } } From ed871c2cb95b493cbee399dce45996df1a2dc778 Mon Sep 17 00:00:00 2001 From: johnsoncodehk Date: Sat, 14 May 2022 04:57:41 +0800 Subject: [PATCH 004/192] refactor: remove `computedToAskDirty` arg from `trigger()` --- packages/reactivity/src/baseHandlers.ts | 4 ++-- packages/reactivity/src/collectionHandlers.ts | 8 ++++---- packages/reactivity/src/effect.ts | 9 ++++----- packages/runtime-core/src/compat/global.ts | 2 +- packages/runtime-core/src/componentProps.ts | 2 +- 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/reactivity/src/baseHandlers.ts b/packages/reactivity/src/baseHandlers.ts index 43bd425d2a7..45ecfa6d38a 100644 --- a/packages/reactivity/src/baseHandlers.ts +++ b/packages/reactivity/src/baseHandlers.ts @@ -190,9 +190,9 @@ function createSetter(shallow = false) { // don't trigger if target is something up in the prototype chain of original if (target === toRaw(receiver)) { if (!hadKey) { - trigger(target, TriggerOpTypes.ADD, undefined, key, value) + trigger(target, TriggerOpTypes.ADD, key, value) } else if (hasChanged(value, oldValue)) { - trigger(target, TriggerOpTypes.SET, undefined, key, value, oldValue) + trigger(target, TriggerOpTypes.SET, key, value, oldValue) } } return result diff --git a/packages/reactivity/src/collectionHandlers.ts b/packages/reactivity/src/collectionHandlers.ts index 651cb1e289f..381bbad6c28 100644 --- a/packages/reactivity/src/collectionHandlers.ts +++ b/packages/reactivity/src/collectionHandlers.ts @@ -73,7 +73,7 @@ function add(this: SetTypes, value: unknown) { const hadKey = proto.has.call(target, value) if (!hadKey) { target.add(value) - trigger(target, TriggerOpTypes.ADD, undefined, value, value) + trigger(target, TriggerOpTypes.ADD, value, value) } return this } @@ -94,9 +94,9 @@ function set(this: MapTypes, key: unknown, value: unknown) { const oldValue = get.call(target, key) target.set(key, value) if (!hadKey) { - trigger(target, TriggerOpTypes.ADD, undefined, key, value) + trigger(target, TriggerOpTypes.ADD, key, value) } else if (hasChanged(value, oldValue)) { - trigger(target, TriggerOpTypes.SET, undefined, key, value, oldValue) + trigger(target, TriggerOpTypes.SET, key, value, oldValue) } return this } @@ -116,7 +116,7 @@ function deleteEntry(this: CollectionTypes, key: unknown) { // forward the operation before queueing reactions const result = target.delete(key) if (hadKey) { - trigger(target, TriggerOpTypes.DELETE, undefined, key, undefined, oldValue) + trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue) } return result } diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 14c2339478c..1ef95b31cd7 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -305,7 +305,6 @@ export function trackEffects( export function trigger( target: object, type: TriggerOpTypes, - computedToAskDirty: ComputedRefImpl | undefined, key?: unknown, newValue?: unknown, oldValue?: unknown, @@ -371,9 +370,9 @@ export function trigger( if (deps.length === 1) { if (deps[0]) { if (__DEV__) { - triggerEffects(deps[0], computedToAskDirty, eventInfo) + triggerEffects(deps[0], undefined, eventInfo) } else { - triggerEffects(deps[0], computedToAskDirty) + triggerEffects(deps[0], undefined) } } } else { @@ -384,9 +383,9 @@ export function trigger( } } if (__DEV__) { - triggerEffects(createDep(effects), computedToAskDirty, eventInfo) + triggerEffects(createDep(effects), undefined, eventInfo) } else { - triggerEffects(createDep(effects), computedToAskDirty) + triggerEffects(createDep(effects), undefined) } } } diff --git a/packages/runtime-core/src/compat/global.ts b/packages/runtime-core/src/compat/global.ts index 1cd9e886984..9f1a6d1cd12 100644 --- a/packages/runtime-core/src/compat/global.ts +++ b/packages/runtime-core/src/compat/global.ts @@ -648,7 +648,7 @@ function defineReactiveSimple(obj: any, key: string, val: any) { }, set(newVal) { val = isObject(newVal) ? reactive(newVal) : newVal - trigger(obj, TriggerOpTypes.SET, undefined, key, newVal) + trigger(obj, TriggerOpTypes.SET, key, newVal) } }) } diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index b1179647871..186384d5b82 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -333,7 +333,7 @@ export function updateProps( // trigger updates for $attrs in case it's used in component slots if (hasAttrsChanged) { - trigger(instance, TriggerOpTypes.SET, undefined, '$attrs') + trigger(instance, TriggerOpTypes.SET, '$attrs') } if (__DEV__) { From fbf214ab21f25b3f407d549dd79ff3e46faf83cf Mon Sep 17 00:00:00 2001 From: johnsoncodehk Date: Sat, 14 May 2022 05:09:22 +0800 Subject: [PATCH 005/192] chore: use `hasChanged()` instead of `!==` --- packages/reactivity/src/computed.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index e7bd253ea49..4888c1c7d01 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -1,6 +1,6 @@ import { DebuggerOptions, ReactiveEffect } from './effect' import { Ref, trackRefValue, triggerRefValue } from './ref' -import { isFunction, NOOP } from '@vue/shared' +import { hasChanged, isFunction, NOOP } from '@vue/shared' import { ReactiveFlags, toRaw } from './reactive' import { Dep } from './dep' @@ -71,7 +71,7 @@ export class ComputedRefImpl { trackRefValue(self) if (self._dirty || !self._cacheable) { const newValue = self.effect.run()! - if (self._value !== newValue) { + if (hasChanged(self._value, newValue)) { triggerRefValue(this, undefined) } self._value = newValue From 399f1553c889a8b7486191ee56ce961814f26b9d Mon Sep 17 00:00:00 2001 From: johnsoncodehk Date: Sun, 15 May 2022 07:37:45 +0800 Subject: [PATCH 006/192] perf: reduce `triggerRefValue()` triggered --- packages/reactivity/src/computed.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 4888c1c7d01..bc78fc2a54b 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -43,12 +43,13 @@ export class ComputedRefImpl { isSSR: boolean ) { this.effect = new ReactiveEffect(getter, (_c) => { - if (_c) { - this._computedsToAskDirty.push(_c) - triggerRefValue(this, this) - } - else if (!this._dirty) { - this._dirty = true + if (!this._dirty) { + if (_c) { + this._computedsToAskDirty.push(_c) + } + else { + this._dirty = true + } triggerRefValue(this, this) } }) From 0e0c44e74f51037253c02c7186adbffb49cc3a40 Mon Sep 17 00:00:00 2001 From: johnsoncodehk Date: Wed, 11 Jan 2023 13:47:36 +0800 Subject: [PATCH 007/192] perf: avoid duplicate trigger effect to deps --- packages/reactivity/src/computed.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index bc78fc2a54b..c29bf83f1d8 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -36,21 +36,25 @@ export class ComputedRefImpl { public _computedsToAskDirty: ComputedRefImpl[] = [] public _cacheable: boolean + private _triggeredAfterLastEffect = false + constructor( getter: ComputedGetter, private readonly _setter: ComputedSetter, isReadonly: boolean, isSSR: boolean ) { - this.effect = new ReactiveEffect(getter, (_c) => { + this.effect = new ReactiveEffect(getter, _c => { if (!this._dirty) { if (_c) { this._computedsToAskDirty.push(_c) - } - else { + } else { this._dirty = true } - triggerRefValue(this, this) + if (!this._triggeredAfterLastEffect) { + this._triggeredAfterLastEffect = true + triggerRefValue(this, this) + } } }) this.effect.computed = this @@ -77,6 +81,7 @@ export class ComputedRefImpl { } self._value = newValue self._dirty = false + self._triggeredAfterLastEffect = false } self._computedsToAskDirty.length = 0 return self._value From 7768d43175ca58dd7bb8eec00a3db3f13f12ef45 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 2 Feb 2023 10:27:15 +0000 Subject: [PATCH 008/192] fix: avoid track unrelated effects --- packages/reactivity/src/computed.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index c29bf83f1d8..9cae1b26e3a 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -1,4 +1,9 @@ -import { DebuggerOptions, ReactiveEffect } from './effect' +import { + DebuggerOptions, + pauseTracking, + ReactiveEffect, + resetTracking +} from './effect' import { Ref, trackRefValue, triggerRefValue } from './ref' import { hasChanged, isFunction, NOOP } from '@vue/shared' import { ReactiveFlags, toRaw } from './reactive' @@ -65,19 +70,21 @@ export class ComputedRefImpl { get value() { // the computed ref may get wrapped by other proxies e.g. readonly() #3376 const self = toRaw(this) - if (!self._dirty) { + if (!self._dirty && self._computedsToAskDirty.length) { + pauseTracking() for (const computedToAskDirty of self._computedsToAskDirty) { computedToAskDirty.value if (self._dirty) { break } } + resetTracking() } trackRefValue(self) if (self._dirty || !self._cacheable) { const newValue = self.effect.run()! if (hasChanged(self._value, newValue)) { - triggerRefValue(this, undefined) + triggerRefValue(self, undefined) } self._value = newValue self._dirty = false From a353eac19b8636a13b3ab763292acd79bf162f5f Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 2 Feb 2023 10:31:02 +0000 Subject: [PATCH 009/192] chore: make `_computedsToAskDirty` private --- packages/reactivity/src/computed.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 9cae1b26e3a..afb7aa70d67 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -38,9 +38,9 @@ export class ComputedRefImpl { public readonly [ReactiveFlags.IS_READONLY]: boolean = false public _dirty = true - public _computedsToAskDirty: ComputedRefImpl[] = [] public _cacheable: boolean + private _computedsToAskDirty: ComputedRefImpl[] = [] private _triggeredAfterLastEffect = false constructor( From 014dcd5aa45a62927a96424d522d36255b741575 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 2 Feb 2023 10:43:11 +0000 Subject: [PATCH 010/192] fix: jest -> vitest --- packages/reactivity/__tests__/computed.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/reactivity/__tests__/computed.spec.ts b/packages/reactivity/__tests__/computed.spec.ts index 9169d75207c..b7ce4717cdc 100644 --- a/packages/reactivity/__tests__/computed.spec.ts +++ b/packages/reactivity/__tests__/computed.spec.ts @@ -292,8 +292,8 @@ describe('reactivity/computed', () => { }) it('chained computed value on-demand trigger', () => { - const c1Spy = jest.fn() - const c2Spy = jest.fn() + const c1Spy = vi.fn() + const c2Spy = vi.fn() const src = ref(0) const c1 = computed(() => { From eb0f8fae3891dbd841f3c59adbae51fc88a34e8f Mon Sep 17 00:00:00 2001 From: johnsoncodehk Date: Wed, 5 Apr 2023 23:51:49 +0800 Subject: [PATCH 011/192] fix: urgent assessment edge case --- .../reactivity/__tests__/computed.spec.ts | 27 +++++++++++++++++++ packages/reactivity/src/computed.ts | 7 +++++ 2 files changed, 34 insertions(+) diff --git a/packages/reactivity/__tests__/computed.spec.ts b/packages/reactivity/__tests__/computed.spec.ts index b7ce4717cdc..76e2c1066b2 100644 --- a/packages/reactivity/__tests__/computed.spec.ts +++ b/packages/reactivity/__tests__/computed.spec.ts @@ -341,4 +341,31 @@ describe('reactivity/computed', () => { expect(c1Spy).toHaveBeenCalledTimes(104) expect(c2Spy).toHaveBeenCalledTimes(2) }) + + it('chained computed value urgent assessment edge case', () => { + const cSpy = vi.fn() + + const a = ref({ + v: 1 + }) + const b = computed(() => { + return a.value + }) + const c = computed(() => { + cSpy() + return b.value?.v + }) + const d = computed(() => { + if (b.value) { + return c.value + } + return 0 + }) + + d.value + a.value!.v = 2 + a.value = null + d.value + expect(cSpy).toHaveBeenCalledTimes(1) + }) }) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index afb7aa70d67..5a92b42624e 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -72,6 +72,13 @@ export class ComputedRefImpl { const self = toRaw(this) if (!self._dirty && self._computedsToAskDirty.length) { pauseTracking() + if (self._computedsToAskDirty.length >= 2) { + self._computedsToAskDirty = self._computedsToAskDirty.sort((a, b) => { + const aIndex = self.effect.deps.indexOf(a.dep!) + const bIndex = self.effect.deps.indexOf(b.dep!) + return aIndex - bIndex + }) + } for (const computedToAskDirty of self._computedsToAskDirty) { computedToAskDirty.value if (self._dirty) { From f3a007124d93b9a369692e545c5b9378e08736dd Mon Sep 17 00:00:00 2001 From: johnsoncodehk Date: Thu, 6 Apr 2023 02:19:38 +0800 Subject: [PATCH 012/192] feat: more purposeful test --- .../reactivity/__tests__/computed.spec.ts | 57 +++++-------------- 1 file changed, 14 insertions(+), 43 deletions(-) diff --git a/packages/reactivity/__tests__/computed.spec.ts b/packages/reactivity/__tests__/computed.spec.ts index 76e2c1066b2..c18e4367fa1 100644 --- a/packages/reactivity/__tests__/computed.spec.ts +++ b/packages/reactivity/__tests__/computed.spec.ts @@ -292,54 +292,25 @@ describe('reactivity/computed', () => { }) it('chained computed value on-demand trigger', () => { - const c1Spy = vi.fn() - const c2Spy = vi.fn() + const minSpy = vi.fn() + const hourSpy = vi.fn() - const src = ref(0) - const c1 = computed(() => { - c1Spy() - return src.value < 5 + const sec = ref(0) + const min = computed(() => { + minSpy() + return Math.floor(sec.value / 60) }) - const c2 = computed(() => { - c2Spy() - return c1.value ? '< 5' : '>= 5' + const hour = computed(() => { + hourSpy() + return Math.floor(min.value / 60) }) - expect(c1Spy).toHaveBeenCalledTimes(0) - expect(c2Spy).toHaveBeenCalledTimes(0) - - expect(src.value).toBe(0) - expect(c2.value).toBe('< 5') - expect(c1Spy).toHaveBeenCalledTimes(1) - expect(c2Spy).toHaveBeenCalledTimes(1) - - src.value++ - expect(c2.value).toBe('< 5') - expect(c1Spy).toHaveBeenCalledTimes(2) - expect(c2Spy).toHaveBeenCalledTimes(1) - - for (let i = 0; i < 10; i++) { - src.value++ + for (sec.value = 0; sec.value < 1000; sec.value++) { + hour.value } - expect(src.value).toBe(11) - expect(c2.value).toBe('>= 5') - expect(c1Spy).toHaveBeenCalledTimes(3) - expect(c2Spy).toHaveBeenCalledTimes(2) - - src.value++ - expect(src.value).toBe(12) - expect(c2.value).toBe('>= 5') - expect(c1Spy).toHaveBeenCalledTimes(4) - expect(c2Spy).toHaveBeenCalledTimes(2) - - for (let i = 0; i < 100; i++) { - src.value++ - c2.value - } - expect(src.value).toBe(112) - expect(c2.value).toBe('>= 5') - expect(c1Spy).toHaveBeenCalledTimes(104) - expect(c2Spy).toHaveBeenCalledTimes(2) + + expect(minSpy).toHaveBeenCalledTimes(1000) + expect(hourSpy).toHaveBeenCalledTimes(17) }) it('chained computed value urgent assessment edge case', () => { From 998c400cad7dcbe44dabc2b223ab99c0557f5106 Mon Sep 17 00:00:00 2001 From: johnsoncodehk Date: Thu, 6 Apr 2023 16:26:39 +0800 Subject: [PATCH 013/192] chore: add test for effect() on-demand trigger --- .../reactivity/__tests__/computed.spec.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/reactivity/__tests__/computed.spec.ts b/packages/reactivity/__tests__/computed.spec.ts index c18e4367fa1..988bead0cce 100644 --- a/packages/reactivity/__tests__/computed.spec.ts +++ b/packages/reactivity/__tests__/computed.spec.ts @@ -313,6 +313,34 @@ describe('reactivity/computed', () => { expect(hourSpy).toHaveBeenCalledTimes(17) }) + it('effect callback on-demand trigger', () => { + const minSpy = vi.fn() + const hourSpy = vi.fn() + const effectSpy = vi.fn() + + const sec = ref(0) + const min = computed(() => { + minSpy() + return Math.floor(sec.value / 60) + }) + const hour = computed(() => { + hourSpy() + return Math.floor(min.value / 60) + }) + + effect(() => { + effectSpy() + min.value + hour.value + }) + + for (sec.value = 0; sec.value < 1000; sec.value++) {} + + expect(minSpy).toHaveBeenCalledTimes(1001) + expect(hourSpy).toHaveBeenCalledTimes(17) + expect(effectSpy).toHaveBeenCalledTimes(1001) + }) + it('chained computed value urgent assessment edge case', () => { const cSpy = vi.fn() From b5527a17fa0374de2e2f39175788af30db1f594f Mon Sep 17 00:00:00 2001 From: johnsoncodehk Date: Thu, 6 Apr 2023 18:15:15 +0800 Subject: [PATCH 014/192] Revert "chore: add test for effect() on-demand trigger" This reverts commit 998c400cad7dcbe44dabc2b223ab99c0557f5106. --- .../reactivity/__tests__/computed.spec.ts | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/packages/reactivity/__tests__/computed.spec.ts b/packages/reactivity/__tests__/computed.spec.ts index 988bead0cce..c18e4367fa1 100644 --- a/packages/reactivity/__tests__/computed.spec.ts +++ b/packages/reactivity/__tests__/computed.spec.ts @@ -313,34 +313,6 @@ describe('reactivity/computed', () => { expect(hourSpy).toHaveBeenCalledTimes(17) }) - it('effect callback on-demand trigger', () => { - const minSpy = vi.fn() - const hourSpy = vi.fn() - const effectSpy = vi.fn() - - const sec = ref(0) - const min = computed(() => { - minSpy() - return Math.floor(sec.value / 60) - }) - const hour = computed(() => { - hourSpy() - return Math.floor(min.value / 60) - }) - - effect(() => { - effectSpy() - min.value - hour.value - }) - - for (sec.value = 0; sec.value < 1000; sec.value++) {} - - expect(minSpy).toHaveBeenCalledTimes(1001) - expect(hourSpy).toHaveBeenCalledTimes(17) - expect(effectSpy).toHaveBeenCalledTimes(1001) - }) - it('chained computed value urgent assessment edge case', () => { const cSpy = vi.fn() From 3e0c2af3c00a8d74edabe8e5d32c1deae889d205 Mon Sep 17 00:00:00 2001 From: johnsoncodehk Date: Thu, 6 Apr 2023 20:23:24 +0800 Subject: [PATCH 015/192] wip: on-demand effect() --- .../reactivity/__tests__/computed.spec.ts | 22 ++++++ packages/reactivity/src/effect.ts | 73 ++++++++++++++++++- 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/packages/reactivity/__tests__/computed.spec.ts b/packages/reactivity/__tests__/computed.spec.ts index c18e4367fa1..2091ae1dc93 100644 --- a/packages/reactivity/__tests__/computed.spec.ts +++ b/packages/reactivity/__tests__/computed.spec.ts @@ -313,6 +313,28 @@ describe('reactivity/computed', () => { expect(hourSpy).toHaveBeenCalledTimes(17) }) + it('effect callback on-demand trigger', () => { + const effectSpy = vi.fn() + + const sec = ref(0) + const min = computed(() => { + return Math.floor(sec.value / 60) + }) + const hour = computed(() => { + return Math.floor(min.value / 60) + }) + + effect(() => { + effectSpy() + min.value + hour.value + }) + + for (sec.value = 0; sec.value < 1000; sec.value++) {} + + expect(effectSpy).toHaveBeenCalledTimes(17) + }) + it('chained computed value urgent assessment edge case', () => { const cSpy = vi.fn() diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 1ef95b31cd7..a4d675ab411 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -30,7 +30,10 @@ export let trackOpBit = 1 */ const maxMarkerBits = 30 -export type EffectScheduler = (computedToAskDirty: ComputedRefImpl | undefined, ...args: any[]) => any +export type EffectScheduler = ( + computedToAskDirty: ComputedRefImpl | undefined, + ...args: any[] +) => any export type DebuggerEvent = { effect: ReactiveEffect @@ -185,7 +188,26 @@ export function effect( fn = (fn as ReactiveEffectRunner).effect.fn } - const _effect = new ReactiveEffect(fn) + let _dirty = false + let _triggeredAfterLastEffect = false + let _computedsToAskDirty: ComputedRefImpl[] = [] + + const _effect = new ReactiveEffect(fn, _c => { + if (!_dirty) { + if (!_c) { + _dirty = true + } + if (state === EffectState.TRACKING) { + if (_c) { + _computedsToAskDirty.push(_c) + } + if (!_triggeredAfterLastEffect) { + _triggeredAfterLastEffect = true + schedulerCallbacks.push(cb) + } + } + } + }) if (options) { extend(_effect, options) if (options.scope) recordEffectScope(_effect, options.scope) @@ -196,6 +218,32 @@ export function effect( const runner = _effect.run.bind(_effect) as ReactiveEffectRunner runner.effect = _effect return runner + + function cb() { + if (!_dirty && _computedsToAskDirty.length) { + pauseTracking() + if (_computedsToAskDirty.length >= 2) { + _computedsToAskDirty = _computedsToAskDirty.sort((a, b) => { + const aIndex = _effect.deps.indexOf(a.dep!) + const bIndex = _effect.deps.indexOf(b.dep!) + return aIndex - bIndex + }) + } + for (const computedToAskDirty of _computedsToAskDirty) { + computedToAskDirty.value + if (_dirty) { + break + } + } + resetTracking() + } + if (_dirty) { + _dirty = false + _effect.run() + } + _computedsToAskDirty.length = 0 + _triggeredAfterLastEffect = false + } } /** @@ -409,11 +457,26 @@ export function triggerEffects( } } +const schedulerCallbacks: (() => void)[] = [] + +const enum EffectState { + NOT_TRACKING = 0, + TRACKING = 1, + POST_SCHEDULER = 2 +} + +let state = EffectState.NOT_TRACKING + function triggerEffect( effect: ReactiveEffect, computedToAskDirty: ComputedRefImpl | undefined, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { + let isRootEffect = false + if (state === EffectState.NOT_TRACKING) { + state = EffectState.TRACKING + isRootEffect = true + } if (effect !== activeEffect || effect.allowRecurse) { if (__DEV__ && effect.onTrigger) { effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) @@ -424,6 +487,12 @@ function triggerEffect( effect.run() } } + if (isRootEffect) { + state = EffectState.POST_SCHEDULER + schedulerCallbacks.forEach(cb => cb()) + schedulerCallbacks.length = 0 + state = EffectState.NOT_TRACKING + } } export function getDepFromReactive(object: any, key: string | number | symbol) { From 62dc5ee1d132fdbb711a1ae9736293c75593f82a Mon Sep 17 00:00:00 2001 From: johnsoncodehk Date: Fri, 7 Apr 2023 19:05:10 +0800 Subject: [PATCH 016/192] fix: ref newVal arg incorrect --- packages/reactivity/src/ref.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index b131ce58c58..834071a7a5b 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -53,7 +53,11 @@ export function trackRefValue(ref: RefBase) { } } -export function triggerRefValue(ref: RefBase, computedToAskDirty: ComputedRefImpl | undefined, newVal?: any) { +export function triggerRefValue( + ref: RefBase, + computedToAskDirty: ComputedRefImpl | undefined, + newVal?: any +) { ref = toRaw(ref) const dep = ref.dep if (dep) { @@ -187,7 +191,7 @@ class RefImpl { * @see {@link https://vuejs.org/api/reactivity-advanced.html#triggerref} */ export function triggerRef(ref: Ref) { - triggerRefValue(ref, __DEV__ ? ref.value : void 0) + triggerRefValue(ref, undefined, __DEV__ ? ref.value : void 0) } export type MaybeRef = T | Ref From 889511719721b754e774b42f1bc3e43391a30f35 Mon Sep 17 00:00:00 2001 From: Haoqun Jiang Date: Fri, 7 Apr 2023 20:18:53 +0800 Subject: [PATCH 017/192] release: v3.3.0-alpha.9 --- CHANGELOG.md | 38 ++++++++++++++++ package.json | 2 +- packages/compiler-core/package.json | 4 +- packages/compiler-dom/package.json | 6 +-- packages/compiler-sfc/package.json | 12 ++--- packages/compiler-ssr/package.json | 6 +-- packages/dts-test/package.json | 2 +- packages/reactivity-transform/package.json | 6 +-- packages/reactivity/package.json | 4 +- packages/runtime-core/package.json | 6 +-- packages/runtime-dom/package.json | 6 +-- packages/runtime-test/package.json | 6 +-- packages/server-renderer/package.json | 8 ++-- packages/sfc-playground/package.json | 2 +- packages/shared/package.json | 2 +- packages/size-check/package.json | 2 +- packages/template-explorer/package.json | 2 +- packages/vue-compat/package.json | 4 +- packages/vue/package.json | 12 ++--- pnpm-lock.yaml | 52 +++++++++++----------- 20 files changed, 110 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c7ba846f5c..5841b02aaa8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,41 @@ +# [3.3.0-alpha.9](https://github.com/vuejs/core/compare/v3.3.0-alpha.8...v3.3.0-alpha.9) (2023-04-07) + + +### Bug Fixes + +* avoid track unrelated effects ([7768d43](https://github.com/vuejs/core/commit/7768d43175ca58dd7bb8eec00a3db3f13f12ef45)) +* **compiler-sfc:** skip empty `defineOptions` and support TypeScript type assertions ([#8028](https://github.com/vuejs/core/issues/8028)) ([9557529](https://github.com/vuejs/core/commit/955752951e1d31b90d817bd20830fe3f89018771)) +* **compiler-ssr:** disable v-once transform in ssr vdom fallback branch ([05f94cf](https://github.com/vuejs/core/commit/05f94cf7b01dd05ed7d3170916a38b175d5df292)), closes [#7644](https://github.com/vuejs/core/issues/7644) +* computed effect dons't transfer ([208e59c](https://github.com/vuejs/core/commit/208e59c9d9e44765b2bd6eccbf7f6f395ae18979)) +* jest -> vitest ([014dcd5](https://github.com/vuejs/core/commit/014dcd5aa45a62927a96424d522d36255b741575)) +* ref newVal arg incorrect ([62dc5ee](https://github.com/vuejs/core/commit/62dc5ee1d132fdbb711a1ae9736293c75593f82a)) +* revert effects trigger order ([4a3e554](https://github.com/vuejs/core/commit/4a3e554d56a0e35c709801a087aadaaf956cd380)) +* **types:** improve defineProps return type with generic arguments ([91a931a](https://github.com/vuejs/core/commit/91a931ae8707b8d43f10216e1ce8e18b12158f99)) +* **types:** more public type argument order fix ([af563bf](https://github.com/vuejs/core/commit/af563bf428200367b6f5bb7944f690c85d810202)) +* **types:** retain type parameters order for public types ([bdf557f](https://github.com/vuejs/core/commit/bdf557f6f233c039fff8007b1b16aec00c4e68aa)) +* urgent assessment edge case ([eb0f8fa](https://github.com/vuejs/core/commit/eb0f8fae3891dbd841f3c59adbae51fc88a34e8f)) + + +### Features + +* **app:** app.runWithContext() ([#7451](https://github.com/vuejs/core/issues/7451)) ([869f3fb](https://github.com/vuejs/core/commit/869f3fb93e61400be4fd925e0850c2b1564749e2)) +* more purposeful test ([f3a0071](https://github.com/vuejs/core/commit/f3a007124d93b9a369692e545c5b9378e08736dd)) + + +### Performance Improvements + +* avoid duplicate trigger effect to deps ([0e0c44e](https://github.com/vuejs/core/commit/0e0c44e74f51037253c02c7186adbffb49cc3a40)) +* calculate embedded `computed()` on-demand ([f485ea0](https://github.com/vuejs/core/commit/f485ea01a9d1d7aca3bdc61b12f768de91c8d583)) +* reduce `triggerRefValue()` triggered ([399f155](https://github.com/vuejs/core/commit/399f1553c889a8b7486191ee56ce961814f26b9d)) + + +### Reverts + +* Revert "chore: add test for effect() on-demand trigger" ([b5527a1](https://github.com/vuejs/core/commit/b5527a17fa0374de2e2f39175788af30db1f594f)) +* Revert "chore: remove unused args passed to ssrRender" ([b117b88](https://github.com/vuejs/core/commit/b117b8844881a732a021432066230ff2215049ea)) + + + # [3.3.0-alpha.8](https://github.com/vuejs/core/compare/v3.3.0-alpha.7...v3.3.0-alpha.8) (2023-04-04) diff --git a/package.json b/package.json index 2f6db271c76..7da3028e73a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "3.3.0-alpha.8", + "version": "3.3.0-alpha.9", "packageManager": "pnpm@7.26.0", "type": "module", "scripts": { diff --git a/packages/compiler-core/package.json b/packages/compiler-core/package.json index 152fc9ce6cf..a7c20e8e1b8 100644 --- a/packages/compiler-core/package.json +++ b/packages/compiler-core/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-core", - "version": "3.3.0-alpha.8", + "version": "3.3.0-alpha.9", "description": "@vue/compiler-core", "main": "index.js", "module": "dist/compiler-core.esm-bundler.js", @@ -33,7 +33,7 @@ "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-core#readme", "dependencies": { "@babel/parser": "^7.21.3", - "@vue/shared": "3.3.0-alpha.8", + "@vue/shared": "3.3.0-alpha.9", "estree-walker": "^2.0.2", "source-map": "^0.6.1" }, diff --git a/packages/compiler-dom/package.json b/packages/compiler-dom/package.json index 007568fec7c..a73978cec7e 100644 --- a/packages/compiler-dom/package.json +++ b/packages/compiler-dom/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-dom", - "version": "3.3.0-alpha.8", + "version": "3.3.0-alpha.9", "description": "@vue/compiler-dom", "main": "index.js", "module": "dist/compiler-dom.esm-bundler.js", @@ -37,7 +37,7 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-dom#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.8", - "@vue/compiler-core": "3.3.0-alpha.8" + "@vue/shared": "3.3.0-alpha.9", + "@vue/compiler-core": "3.3.0-alpha.9" } } diff --git a/packages/compiler-sfc/package.json b/packages/compiler-sfc/package.json index ce831788a9a..8c97d3b3b9b 100644 --- a/packages/compiler-sfc/package.json +++ b/packages/compiler-sfc/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-sfc", - "version": "3.3.0-alpha.8", + "version": "3.3.0-alpha.9", "description": "@vue/compiler-sfc", "main": "dist/compiler-sfc.cjs.js", "module": "dist/compiler-sfc.esm-browser.js", @@ -33,11 +33,11 @@ "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-sfc#readme", "dependencies": { "@babel/parser": "^7.20.15", - "@vue/compiler-core": "3.3.0-alpha.8", - "@vue/compiler-dom": "3.3.0-alpha.8", - "@vue/compiler-ssr": "3.3.0-alpha.8", - "@vue/reactivity-transform": "3.3.0-alpha.8", - "@vue/shared": "3.3.0-alpha.8", + "@vue/compiler-core": "3.3.0-alpha.9", + "@vue/compiler-dom": "3.3.0-alpha.9", + "@vue/compiler-ssr": "3.3.0-alpha.9", + "@vue/reactivity-transform": "3.3.0-alpha.9", + "@vue/shared": "3.3.0-alpha.9", "estree-walker": "^2.0.2", "magic-string": "^0.30.0", "postcss": "^8.1.10", diff --git a/packages/compiler-ssr/package.json b/packages/compiler-ssr/package.json index b692b9fdd0f..b1809422b1d 100644 --- a/packages/compiler-ssr/package.json +++ b/packages/compiler-ssr/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-ssr", - "version": "3.3.0-alpha.8", + "version": "3.3.0-alpha.9", "description": "@vue/compiler-ssr", "main": "dist/compiler-ssr.cjs.js", "types": "dist/compiler-ssr.d.ts", @@ -28,7 +28,7 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-ssr#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.8", - "@vue/compiler-dom": "3.3.0-alpha.8" + "@vue/shared": "3.3.0-alpha.9", + "@vue/compiler-dom": "3.3.0-alpha.9" } } diff --git a/packages/dts-test/package.json b/packages/dts-test/package.json index 5d5acc0989d..833b92e5008 100644 --- a/packages/dts-test/package.json +++ b/packages/dts-test/package.json @@ -4,5 +4,5 @@ "dependencies": { "vue": "workspace:*" }, - "version": "3.3.0-alpha.8" + "version": "3.3.0-alpha.9" } diff --git a/packages/reactivity-transform/package.json b/packages/reactivity-transform/package.json index 29f37dd6a49..5045a40a818 100644 --- a/packages/reactivity-transform/package.json +++ b/packages/reactivity-transform/package.json @@ -1,6 +1,6 @@ { "name": "@vue/reactivity-transform", - "version": "3.3.0-alpha.8", + "version": "3.3.0-alpha.9", "description": "@vue/reactivity-transform", "main": "dist/reactivity-transform.cjs.js", "files": [ @@ -29,8 +29,8 @@ "homepage": "https://github.com/vuejs/core/tree/dev/packages/reactivity-transform#readme", "dependencies": { "@babel/parser": "^7.20.15", - "@vue/compiler-core": "3.3.0-alpha.8", - "@vue/shared": "3.3.0-alpha.8", + "@vue/compiler-core": "3.3.0-alpha.9", + "@vue/shared": "3.3.0-alpha.9", "estree-walker": "^2.0.2", "magic-string": "^0.30.0" }, diff --git a/packages/reactivity/package.json b/packages/reactivity/package.json index e07312dce31..4c7988e82a6 100644 --- a/packages/reactivity/package.json +++ b/packages/reactivity/package.json @@ -1,6 +1,6 @@ { "name": "@vue/reactivity", - "version": "3.3.0-alpha.8", + "version": "3.3.0-alpha.9", "description": "@vue/reactivity", "main": "index.js", "module": "dist/reactivity.esm-bundler.js", @@ -36,6 +36,6 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/reactivity#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.8" + "@vue/shared": "3.3.0-alpha.9" } } diff --git a/packages/runtime-core/package.json b/packages/runtime-core/package.json index a282b8d87b4..606fef5414a 100644 --- a/packages/runtime-core/package.json +++ b/packages/runtime-core/package.json @@ -1,6 +1,6 @@ { "name": "@vue/runtime-core", - "version": "3.3.0-alpha.8", + "version": "3.3.0-alpha.9", "description": "@vue/runtime-core", "main": "index.js", "module": "dist/runtime-core.esm-bundler.js", @@ -32,7 +32,7 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-core#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.8", - "@vue/reactivity": "3.3.0-alpha.8" + "@vue/shared": "3.3.0-alpha.9", + "@vue/reactivity": "3.3.0-alpha.9" } } diff --git a/packages/runtime-dom/package.json b/packages/runtime-dom/package.json index 29cf32c048b..25da3f9657a 100644 --- a/packages/runtime-dom/package.json +++ b/packages/runtime-dom/package.json @@ -1,6 +1,6 @@ { "name": "@vue/runtime-dom", - "version": "3.3.0-alpha.8", + "version": "3.3.0-alpha.9", "description": "@vue/runtime-dom", "main": "index.js", "module": "dist/runtime-dom.esm-bundler.js", @@ -35,8 +35,8 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-dom#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.8", - "@vue/runtime-core": "3.3.0-alpha.8", + "@vue/shared": "3.3.0-alpha.9", + "@vue/runtime-core": "3.3.0-alpha.9", "csstype": "^3.1.1" } } diff --git a/packages/runtime-test/package.json b/packages/runtime-test/package.json index 4c778451a92..202161c9474 100644 --- a/packages/runtime-test/package.json +++ b/packages/runtime-test/package.json @@ -1,6 +1,6 @@ { "name": "@vue/runtime-test", - "version": "3.3.0-alpha.8", + "version": "3.3.0-alpha.9", "description": "@vue/runtime-test", "private": true, "main": "index.js", @@ -25,7 +25,7 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-test#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.8", - "@vue/runtime-core": "3.3.0-alpha.8" + "@vue/shared": "3.3.0-alpha.9", + "@vue/runtime-core": "3.3.0-alpha.9" } } diff --git a/packages/server-renderer/package.json b/packages/server-renderer/package.json index d6d43bf82f7..9f8a2f8eafd 100644 --- a/packages/server-renderer/package.json +++ b/packages/server-renderer/package.json @@ -1,6 +1,6 @@ { "name": "@vue/server-renderer", - "version": "3.3.0-alpha.8", + "version": "3.3.0-alpha.9", "description": "@vue/server-renderer", "main": "index.js", "module": "dist/server-renderer.esm-bundler.js", @@ -32,10 +32,10 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/server-renderer#readme", "peerDependencies": { - "vue": "3.3.0-alpha.8" + "vue": "3.3.0-alpha.9" }, "dependencies": { - "@vue/shared": "3.3.0-alpha.8", - "@vue/compiler-ssr": "3.3.0-alpha.8" + "@vue/shared": "3.3.0-alpha.9", + "@vue/compiler-ssr": "3.3.0-alpha.9" } } diff --git a/packages/sfc-playground/package.json b/packages/sfc-playground/package.json index 214e81cce27..e434dd9495b 100644 --- a/packages/sfc-playground/package.json +++ b/packages/sfc-playground/package.json @@ -1,6 +1,6 @@ { "name": "@vue/sfc-playground", - "version": "3.3.0-alpha.8", + "version": "3.3.0-alpha.9", "private": true, "scripts": { "dev": "vite", diff --git a/packages/shared/package.json b/packages/shared/package.json index 727e24ee626..a7ceb93b4ae 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "name": "@vue/shared", - "version": "3.3.0-alpha.8", + "version": "3.3.0-alpha.9", "description": "internal utils shared across @vue packages", "main": "index.js", "module": "dist/shared.esm-bundler.js", diff --git a/packages/size-check/package.json b/packages/size-check/package.json index 94d49cc4e6a..34c7df41c92 100644 --- a/packages/size-check/package.json +++ b/packages/size-check/package.json @@ -1,6 +1,6 @@ { "name": "@vue/size-check", - "version": "3.3.0-alpha.8", + "version": "3.3.0-alpha.9", "private": true, "scripts": { "build": "vite build" diff --git a/packages/template-explorer/package.json b/packages/template-explorer/package.json index 7b42a4c3ea6..81852352839 100644 --- a/packages/template-explorer/package.json +++ b/packages/template-explorer/package.json @@ -1,6 +1,6 @@ { "name": "@vue/template-explorer", - "version": "3.3.0-alpha.8", + "version": "3.3.0-alpha.9", "private": true, "buildOptions": { "formats": [ diff --git a/packages/vue-compat/package.json b/packages/vue-compat/package.json index aa04139c247..5032c134cf8 100644 --- a/packages/vue-compat/package.json +++ b/packages/vue-compat/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compat", - "version": "3.3.0-alpha.8", + "version": "3.3.0-alpha.9", "description": "Vue 3 compatibility build for Vue 2", "main": "index.js", "module": "dist/vue.runtime.esm-bundler.js", @@ -43,6 +43,6 @@ "source-map": "^0.6.1" }, "peerDependencies": { - "vue": "3.3.0-alpha.8" + "vue": "3.3.0-alpha.9" } } diff --git a/packages/vue/package.json b/packages/vue/package.json index cb17e34700d..5a2e32f0e5a 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "vue", - "version": "3.3.0-alpha.8", + "version": "3.3.0-alpha.9", "description": "The progressive JavaScript framework for building modern web UI.", "main": "index.js", "module": "dist/vue.runtime.esm-bundler.js", @@ -81,10 +81,10 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/vue#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.8", - "@vue/compiler-dom": "3.3.0-alpha.8", - "@vue/runtime-dom": "3.3.0-alpha.8", - "@vue/compiler-sfc": "3.3.0-alpha.8", - "@vue/server-renderer": "3.3.0-alpha.8" + "@vue/shared": "3.3.0-alpha.9", + "@vue/compiler-dom": "3.3.0-alpha.9", + "@vue/runtime-dom": "3.3.0-alpha.9", + "@vue/compiler-sfc": "3.3.0-alpha.9", + "@vue/server-renderer": "3.3.0-alpha.9" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5206450c2af..489f7892681 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -102,7 +102,7 @@ importers: specifiers: '@babel/parser': ^7.21.3 '@babel/types': ^7.21.3 - '@vue/shared': 3.3.0-alpha.8 + '@vue/shared': 3.3.0-alpha.9 estree-walker: ^2.0.2 source-map: ^0.6.1 dependencies: @@ -115,8 +115,8 @@ importers: packages/compiler-dom: specifiers: - '@vue/compiler-core': 3.3.0-alpha.8 - '@vue/shared': 3.3.0-alpha.8 + '@vue/compiler-core': 3.3.0-alpha.9 + '@vue/shared': 3.3.0-alpha.9 dependencies: '@vue/compiler-core': link:../compiler-core '@vue/shared': link:../shared @@ -127,12 +127,12 @@ importers: '@babel/types': ^7.21.3 '@types/estree': ^0.0.48 '@types/lru-cache': ^5.1.0 - '@vue/compiler-core': 3.3.0-alpha.8 - '@vue/compiler-dom': 3.3.0-alpha.8 - '@vue/compiler-ssr': 3.3.0-alpha.8 + '@vue/compiler-core': 3.3.0-alpha.9 + '@vue/compiler-dom': 3.3.0-alpha.9 + '@vue/compiler-ssr': 3.3.0-alpha.9 '@vue/consolidate': ^0.17.3 - '@vue/reactivity-transform': 3.3.0-alpha.8 - '@vue/shared': 3.3.0-alpha.8 + '@vue/reactivity-transform': 3.3.0-alpha.9 + '@vue/shared': 3.3.0-alpha.9 estree-walker: ^2.0.2 hash-sum: ^2.0.0 lru-cache: ^5.1.1 @@ -170,8 +170,8 @@ importers: packages/compiler-ssr: specifiers: - '@vue/compiler-dom': 3.3.0-alpha.8 - '@vue/shared': 3.3.0-alpha.8 + '@vue/compiler-dom': 3.3.0-alpha.9 + '@vue/shared': 3.3.0-alpha.9 dependencies: '@vue/compiler-dom': link:../compiler-dom '@vue/shared': link:../shared @@ -184,7 +184,7 @@ importers: packages/reactivity: specifiers: - '@vue/shared': 3.3.0-alpha.8 + '@vue/shared': 3.3.0-alpha.9 dependencies: '@vue/shared': link:../shared @@ -193,8 +193,8 @@ importers: '@babel/core': ^7.21.3 '@babel/parser': ^7.20.15 '@babel/types': ^7.21.3 - '@vue/compiler-core': 3.3.0-alpha.8 - '@vue/shared': 3.3.0-alpha.8 + '@vue/compiler-core': 3.3.0-alpha.9 + '@vue/shared': 3.3.0-alpha.9 estree-walker: ^2.0.2 magic-string: ^0.30.0 dependencies: @@ -209,16 +209,16 @@ importers: packages/runtime-core: specifiers: - '@vue/reactivity': 3.3.0-alpha.8 - '@vue/shared': 3.3.0-alpha.8 + '@vue/reactivity': 3.3.0-alpha.9 + '@vue/shared': 3.3.0-alpha.9 dependencies: '@vue/reactivity': link:../reactivity '@vue/shared': link:../shared packages/runtime-dom: specifiers: - '@vue/runtime-core': 3.3.0-alpha.8 - '@vue/shared': 3.3.0-alpha.8 + '@vue/runtime-core': 3.3.0-alpha.9 + '@vue/shared': 3.3.0-alpha.9 csstype: ^3.1.1 dependencies: '@vue/runtime-core': link:../runtime-core @@ -227,16 +227,16 @@ importers: packages/runtime-test: specifiers: - '@vue/runtime-core': 3.3.0-alpha.8 - '@vue/shared': 3.3.0-alpha.8 + '@vue/runtime-core': 3.3.0-alpha.9 + '@vue/shared': 3.3.0-alpha.9 dependencies: '@vue/runtime-core': link:../runtime-core '@vue/shared': link:../shared packages/server-renderer: specifiers: - '@vue/compiler-ssr': 3.3.0-alpha.8 - '@vue/shared': 3.3.0-alpha.8 + '@vue/compiler-ssr': 3.3.0-alpha.9 + '@vue/shared': 3.3.0-alpha.9 dependencies: '@vue/compiler-ssr': link:../compiler-ssr '@vue/shared': link:../shared @@ -277,11 +277,11 @@ importers: packages/vue: specifiers: - '@vue/compiler-dom': 3.3.0-alpha.8 - '@vue/compiler-sfc': 3.3.0-alpha.8 - '@vue/runtime-dom': 3.3.0-alpha.8 - '@vue/server-renderer': 3.3.0-alpha.8 - '@vue/shared': 3.3.0-alpha.8 + '@vue/compiler-dom': 3.3.0-alpha.9 + '@vue/compiler-sfc': 3.3.0-alpha.9 + '@vue/runtime-dom': 3.3.0-alpha.9 + '@vue/server-renderer': 3.3.0-alpha.9 + '@vue/shared': 3.3.0-alpha.9 dependencies: '@vue/compiler-dom': link:../compiler-dom '@vue/compiler-sfc': link:../compiler-sfc From 909afcce8204c6ac659e57f870d918b2ddee5ab6 Mon Sep 17 00:00:00 2001 From: johnsoncodehk Date: Fri, 7 Apr 2023 21:18:47 +0800 Subject: [PATCH 018/192] fix: fixed effect for tests --- packages/reactivity/src/effect.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index a4d675ab411..34dc6ff7eab 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -201,10 +201,13 @@ export function effect( if (_c) { _computedsToAskDirty.push(_c) } - if (!_triggeredAfterLastEffect) { - _triggeredAfterLastEffect = true - schedulerCallbacks.push(cb) - } + } + if ( + (_dirty || _computedsToAskDirty.length) && + !_triggeredAfterLastEffect + ) { + _triggeredAfterLastEffect = true + schedulerCallbacks.push(cb) } } }) @@ -489,8 +492,9 @@ function triggerEffect( } if (isRootEffect) { state = EffectState.POST_SCHEDULER - schedulerCallbacks.forEach(cb => cb()) - schedulerCallbacks.length = 0 + while (schedulerCallbacks.length) { + schedulerCallbacks.shift()!() + } state = EffectState.NOT_TRACKING } } From da6263cb9d41c0199b91f47f26c76f778e1ead9b Mon Sep 17 00:00:00 2001 From: johnsoncodehk Date: Fri, 7 Apr 2023 21:31:00 +0800 Subject: [PATCH 019/192] refactor: implement simplify --- packages/reactivity/src/effect.ts | 32 ++++++++----------------------- 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 34dc6ff7eab..3abcf9f88de 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -194,18 +194,12 @@ export function effect( const _effect = new ReactiveEffect(fn, _c => { if (!_dirty) { - if (!_c) { + if (_c) { + _computedsToAskDirty.push(_c) + } else { _dirty = true } - if (state === EffectState.TRACKING) { - if (_c) { - _computedsToAskDirty.push(_c) - } - } - if ( - (_dirty || _computedsToAskDirty.length) && - !_triggeredAfterLastEffect - ) { + if (!_triggeredAfterLastEffect) { _triggeredAfterLastEffect = true schedulerCallbacks.push(cb) } @@ -462,24 +456,15 @@ export function triggerEffects( const schedulerCallbacks: (() => void)[] = [] -const enum EffectState { - NOT_TRACKING = 0, - TRACKING = 1, - POST_SCHEDULER = 2 -} - -let state = EffectState.NOT_TRACKING +let tracking = false function triggerEffect( effect: ReactiveEffect, computedToAskDirty: ComputedRefImpl | undefined, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { - let isRootEffect = false - if (state === EffectState.NOT_TRACKING) { - state = EffectState.TRACKING - isRootEffect = true - } + let isRootEffect = !tracking + tracking = true if (effect !== activeEffect || effect.allowRecurse) { if (__DEV__ && effect.onTrigger) { effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) @@ -491,11 +476,10 @@ function triggerEffect( } } if (isRootEffect) { - state = EffectState.POST_SCHEDULER while (schedulerCallbacks.length) { schedulerCallbacks.shift()!() } - state = EffectState.NOT_TRACKING + tracking = false } } From d2fd264982451467a2aa4089c92ac3ebf6101034 Mon Sep 17 00:00:00 2001 From: Haoqun Jiang Date: Fri, 7 Apr 2023 23:01:59 +0800 Subject: [PATCH 020/192] Revert "release: v3.3.0-alpha.9" This reverts commit 889511719721b754e774b42f1bc3e43391a30f35. --- CHANGELOG.md | 38 ---------------- package.json | 2 +- packages/compiler-core/package.json | 4 +- packages/compiler-dom/package.json | 6 +-- packages/compiler-sfc/package.json | 12 ++--- packages/compiler-ssr/package.json | 6 +-- packages/dts-test/package.json | 2 +- packages/reactivity-transform/package.json | 6 +-- packages/reactivity/package.json | 4 +- packages/runtime-core/package.json | 6 +-- packages/runtime-dom/package.json | 6 +-- packages/runtime-test/package.json | 6 +-- packages/server-renderer/package.json | 8 ++-- packages/sfc-playground/package.json | 2 +- packages/shared/package.json | 2 +- packages/size-check/package.json | 2 +- packages/template-explorer/package.json | 2 +- packages/vue-compat/package.json | 4 +- packages/vue/package.json | 12 ++--- pnpm-lock.yaml | 52 +++++++++++----------- 20 files changed, 72 insertions(+), 110 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5841b02aaa8..1c7ba846f5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,41 +1,3 @@ -# [3.3.0-alpha.9](https://github.com/vuejs/core/compare/v3.3.0-alpha.8...v3.3.0-alpha.9) (2023-04-07) - - -### Bug Fixes - -* avoid track unrelated effects ([7768d43](https://github.com/vuejs/core/commit/7768d43175ca58dd7bb8eec00a3db3f13f12ef45)) -* **compiler-sfc:** skip empty `defineOptions` and support TypeScript type assertions ([#8028](https://github.com/vuejs/core/issues/8028)) ([9557529](https://github.com/vuejs/core/commit/955752951e1d31b90d817bd20830fe3f89018771)) -* **compiler-ssr:** disable v-once transform in ssr vdom fallback branch ([05f94cf](https://github.com/vuejs/core/commit/05f94cf7b01dd05ed7d3170916a38b175d5df292)), closes [#7644](https://github.com/vuejs/core/issues/7644) -* computed effect dons't transfer ([208e59c](https://github.com/vuejs/core/commit/208e59c9d9e44765b2bd6eccbf7f6f395ae18979)) -* jest -> vitest ([014dcd5](https://github.com/vuejs/core/commit/014dcd5aa45a62927a96424d522d36255b741575)) -* ref newVal arg incorrect ([62dc5ee](https://github.com/vuejs/core/commit/62dc5ee1d132fdbb711a1ae9736293c75593f82a)) -* revert effects trigger order ([4a3e554](https://github.com/vuejs/core/commit/4a3e554d56a0e35c709801a087aadaaf956cd380)) -* **types:** improve defineProps return type with generic arguments ([91a931a](https://github.com/vuejs/core/commit/91a931ae8707b8d43f10216e1ce8e18b12158f99)) -* **types:** more public type argument order fix ([af563bf](https://github.com/vuejs/core/commit/af563bf428200367b6f5bb7944f690c85d810202)) -* **types:** retain type parameters order for public types ([bdf557f](https://github.com/vuejs/core/commit/bdf557f6f233c039fff8007b1b16aec00c4e68aa)) -* urgent assessment edge case ([eb0f8fa](https://github.com/vuejs/core/commit/eb0f8fae3891dbd841f3c59adbae51fc88a34e8f)) - - -### Features - -* **app:** app.runWithContext() ([#7451](https://github.com/vuejs/core/issues/7451)) ([869f3fb](https://github.com/vuejs/core/commit/869f3fb93e61400be4fd925e0850c2b1564749e2)) -* more purposeful test ([f3a0071](https://github.com/vuejs/core/commit/f3a007124d93b9a369692e545c5b9378e08736dd)) - - -### Performance Improvements - -* avoid duplicate trigger effect to deps ([0e0c44e](https://github.com/vuejs/core/commit/0e0c44e74f51037253c02c7186adbffb49cc3a40)) -* calculate embedded `computed()` on-demand ([f485ea0](https://github.com/vuejs/core/commit/f485ea01a9d1d7aca3bdc61b12f768de91c8d583)) -* reduce `triggerRefValue()` triggered ([399f155](https://github.com/vuejs/core/commit/399f1553c889a8b7486191ee56ce961814f26b9d)) - - -### Reverts - -* Revert "chore: add test for effect() on-demand trigger" ([b5527a1](https://github.com/vuejs/core/commit/b5527a17fa0374de2e2f39175788af30db1f594f)) -* Revert "chore: remove unused args passed to ssrRender" ([b117b88](https://github.com/vuejs/core/commit/b117b8844881a732a021432066230ff2215049ea)) - - - # [3.3.0-alpha.8](https://github.com/vuejs/core/compare/v3.3.0-alpha.7...v3.3.0-alpha.8) (2023-04-04) diff --git a/package.json b/package.json index 7da3028e73a..2f6db271c76 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.8", "packageManager": "pnpm@7.26.0", "type": "module", "scripts": { diff --git a/packages/compiler-core/package.json b/packages/compiler-core/package.json index a7c20e8e1b8..152fc9ce6cf 100644 --- a/packages/compiler-core/package.json +++ b/packages/compiler-core/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-core", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.8", "description": "@vue/compiler-core", "main": "index.js", "module": "dist/compiler-core.esm-bundler.js", @@ -33,7 +33,7 @@ "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-core#readme", "dependencies": { "@babel/parser": "^7.21.3", - "@vue/shared": "3.3.0-alpha.9", + "@vue/shared": "3.3.0-alpha.8", "estree-walker": "^2.0.2", "source-map": "^0.6.1" }, diff --git a/packages/compiler-dom/package.json b/packages/compiler-dom/package.json index a73978cec7e..007568fec7c 100644 --- a/packages/compiler-dom/package.json +++ b/packages/compiler-dom/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-dom", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.8", "description": "@vue/compiler-dom", "main": "index.js", "module": "dist/compiler-dom.esm-bundler.js", @@ -37,7 +37,7 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-dom#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.9", - "@vue/compiler-core": "3.3.0-alpha.9" + "@vue/shared": "3.3.0-alpha.8", + "@vue/compiler-core": "3.3.0-alpha.8" } } diff --git a/packages/compiler-sfc/package.json b/packages/compiler-sfc/package.json index 8c97d3b3b9b..ce831788a9a 100644 --- a/packages/compiler-sfc/package.json +++ b/packages/compiler-sfc/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-sfc", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.8", "description": "@vue/compiler-sfc", "main": "dist/compiler-sfc.cjs.js", "module": "dist/compiler-sfc.esm-browser.js", @@ -33,11 +33,11 @@ "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-sfc#readme", "dependencies": { "@babel/parser": "^7.20.15", - "@vue/compiler-core": "3.3.0-alpha.9", - "@vue/compiler-dom": "3.3.0-alpha.9", - "@vue/compiler-ssr": "3.3.0-alpha.9", - "@vue/reactivity-transform": "3.3.0-alpha.9", - "@vue/shared": "3.3.0-alpha.9", + "@vue/compiler-core": "3.3.0-alpha.8", + "@vue/compiler-dom": "3.3.0-alpha.8", + "@vue/compiler-ssr": "3.3.0-alpha.8", + "@vue/reactivity-transform": "3.3.0-alpha.8", + "@vue/shared": "3.3.0-alpha.8", "estree-walker": "^2.0.2", "magic-string": "^0.30.0", "postcss": "^8.1.10", diff --git a/packages/compiler-ssr/package.json b/packages/compiler-ssr/package.json index b1809422b1d..b692b9fdd0f 100644 --- a/packages/compiler-ssr/package.json +++ b/packages/compiler-ssr/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-ssr", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.8", "description": "@vue/compiler-ssr", "main": "dist/compiler-ssr.cjs.js", "types": "dist/compiler-ssr.d.ts", @@ -28,7 +28,7 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-ssr#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.9", - "@vue/compiler-dom": "3.3.0-alpha.9" + "@vue/shared": "3.3.0-alpha.8", + "@vue/compiler-dom": "3.3.0-alpha.8" } } diff --git a/packages/dts-test/package.json b/packages/dts-test/package.json index 833b92e5008..5d5acc0989d 100644 --- a/packages/dts-test/package.json +++ b/packages/dts-test/package.json @@ -4,5 +4,5 @@ "dependencies": { "vue": "workspace:*" }, - "version": "3.3.0-alpha.9" + "version": "3.3.0-alpha.8" } diff --git a/packages/reactivity-transform/package.json b/packages/reactivity-transform/package.json index 5045a40a818..29f37dd6a49 100644 --- a/packages/reactivity-transform/package.json +++ b/packages/reactivity-transform/package.json @@ -1,6 +1,6 @@ { "name": "@vue/reactivity-transform", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.8", "description": "@vue/reactivity-transform", "main": "dist/reactivity-transform.cjs.js", "files": [ @@ -29,8 +29,8 @@ "homepage": "https://github.com/vuejs/core/tree/dev/packages/reactivity-transform#readme", "dependencies": { "@babel/parser": "^7.20.15", - "@vue/compiler-core": "3.3.0-alpha.9", - "@vue/shared": "3.3.0-alpha.9", + "@vue/compiler-core": "3.3.0-alpha.8", + "@vue/shared": "3.3.0-alpha.8", "estree-walker": "^2.0.2", "magic-string": "^0.30.0" }, diff --git a/packages/reactivity/package.json b/packages/reactivity/package.json index 4c7988e82a6..e07312dce31 100644 --- a/packages/reactivity/package.json +++ b/packages/reactivity/package.json @@ -1,6 +1,6 @@ { "name": "@vue/reactivity", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.8", "description": "@vue/reactivity", "main": "index.js", "module": "dist/reactivity.esm-bundler.js", @@ -36,6 +36,6 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/reactivity#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.9" + "@vue/shared": "3.3.0-alpha.8" } } diff --git a/packages/runtime-core/package.json b/packages/runtime-core/package.json index 606fef5414a..a282b8d87b4 100644 --- a/packages/runtime-core/package.json +++ b/packages/runtime-core/package.json @@ -1,6 +1,6 @@ { "name": "@vue/runtime-core", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.8", "description": "@vue/runtime-core", "main": "index.js", "module": "dist/runtime-core.esm-bundler.js", @@ -32,7 +32,7 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-core#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.9", - "@vue/reactivity": "3.3.0-alpha.9" + "@vue/shared": "3.3.0-alpha.8", + "@vue/reactivity": "3.3.0-alpha.8" } } diff --git a/packages/runtime-dom/package.json b/packages/runtime-dom/package.json index 25da3f9657a..29cf32c048b 100644 --- a/packages/runtime-dom/package.json +++ b/packages/runtime-dom/package.json @@ -1,6 +1,6 @@ { "name": "@vue/runtime-dom", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.8", "description": "@vue/runtime-dom", "main": "index.js", "module": "dist/runtime-dom.esm-bundler.js", @@ -35,8 +35,8 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-dom#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.9", - "@vue/runtime-core": "3.3.0-alpha.9", + "@vue/shared": "3.3.0-alpha.8", + "@vue/runtime-core": "3.3.0-alpha.8", "csstype": "^3.1.1" } } diff --git a/packages/runtime-test/package.json b/packages/runtime-test/package.json index 202161c9474..4c778451a92 100644 --- a/packages/runtime-test/package.json +++ b/packages/runtime-test/package.json @@ -1,6 +1,6 @@ { "name": "@vue/runtime-test", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.8", "description": "@vue/runtime-test", "private": true, "main": "index.js", @@ -25,7 +25,7 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-test#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.9", - "@vue/runtime-core": "3.3.0-alpha.9" + "@vue/shared": "3.3.0-alpha.8", + "@vue/runtime-core": "3.3.0-alpha.8" } } diff --git a/packages/server-renderer/package.json b/packages/server-renderer/package.json index 9f8a2f8eafd..d6d43bf82f7 100644 --- a/packages/server-renderer/package.json +++ b/packages/server-renderer/package.json @@ -1,6 +1,6 @@ { "name": "@vue/server-renderer", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.8", "description": "@vue/server-renderer", "main": "index.js", "module": "dist/server-renderer.esm-bundler.js", @@ -32,10 +32,10 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/server-renderer#readme", "peerDependencies": { - "vue": "3.3.0-alpha.9" + "vue": "3.3.0-alpha.8" }, "dependencies": { - "@vue/shared": "3.3.0-alpha.9", - "@vue/compiler-ssr": "3.3.0-alpha.9" + "@vue/shared": "3.3.0-alpha.8", + "@vue/compiler-ssr": "3.3.0-alpha.8" } } diff --git a/packages/sfc-playground/package.json b/packages/sfc-playground/package.json index e434dd9495b..214e81cce27 100644 --- a/packages/sfc-playground/package.json +++ b/packages/sfc-playground/package.json @@ -1,6 +1,6 @@ { "name": "@vue/sfc-playground", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.8", "private": true, "scripts": { "dev": "vite", diff --git a/packages/shared/package.json b/packages/shared/package.json index a7ceb93b4ae..727e24ee626 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "name": "@vue/shared", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.8", "description": "internal utils shared across @vue packages", "main": "index.js", "module": "dist/shared.esm-bundler.js", diff --git a/packages/size-check/package.json b/packages/size-check/package.json index 34c7df41c92..94d49cc4e6a 100644 --- a/packages/size-check/package.json +++ b/packages/size-check/package.json @@ -1,6 +1,6 @@ { "name": "@vue/size-check", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.8", "private": true, "scripts": { "build": "vite build" diff --git a/packages/template-explorer/package.json b/packages/template-explorer/package.json index 81852352839..7b42a4c3ea6 100644 --- a/packages/template-explorer/package.json +++ b/packages/template-explorer/package.json @@ -1,6 +1,6 @@ { "name": "@vue/template-explorer", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.8", "private": true, "buildOptions": { "formats": [ diff --git a/packages/vue-compat/package.json b/packages/vue-compat/package.json index 5032c134cf8..aa04139c247 100644 --- a/packages/vue-compat/package.json +++ b/packages/vue-compat/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compat", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.8", "description": "Vue 3 compatibility build for Vue 2", "main": "index.js", "module": "dist/vue.runtime.esm-bundler.js", @@ -43,6 +43,6 @@ "source-map": "^0.6.1" }, "peerDependencies": { - "vue": "3.3.0-alpha.9" + "vue": "3.3.0-alpha.8" } } diff --git a/packages/vue/package.json b/packages/vue/package.json index 5a2e32f0e5a..cb17e34700d 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "vue", - "version": "3.3.0-alpha.9", + "version": "3.3.0-alpha.8", "description": "The progressive JavaScript framework for building modern web UI.", "main": "index.js", "module": "dist/vue.runtime.esm-bundler.js", @@ -81,10 +81,10 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/vue#readme", "dependencies": { - "@vue/shared": "3.3.0-alpha.9", - "@vue/compiler-dom": "3.3.0-alpha.9", - "@vue/runtime-dom": "3.3.0-alpha.9", - "@vue/compiler-sfc": "3.3.0-alpha.9", - "@vue/server-renderer": "3.3.0-alpha.9" + "@vue/shared": "3.3.0-alpha.8", + "@vue/compiler-dom": "3.3.0-alpha.8", + "@vue/runtime-dom": "3.3.0-alpha.8", + "@vue/compiler-sfc": "3.3.0-alpha.8", + "@vue/server-renderer": "3.3.0-alpha.8" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 489f7892681..5206450c2af 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -102,7 +102,7 @@ importers: specifiers: '@babel/parser': ^7.21.3 '@babel/types': ^7.21.3 - '@vue/shared': 3.3.0-alpha.9 + '@vue/shared': 3.3.0-alpha.8 estree-walker: ^2.0.2 source-map: ^0.6.1 dependencies: @@ -115,8 +115,8 @@ importers: packages/compiler-dom: specifiers: - '@vue/compiler-core': 3.3.0-alpha.9 - '@vue/shared': 3.3.0-alpha.9 + '@vue/compiler-core': 3.3.0-alpha.8 + '@vue/shared': 3.3.0-alpha.8 dependencies: '@vue/compiler-core': link:../compiler-core '@vue/shared': link:../shared @@ -127,12 +127,12 @@ importers: '@babel/types': ^7.21.3 '@types/estree': ^0.0.48 '@types/lru-cache': ^5.1.0 - '@vue/compiler-core': 3.3.0-alpha.9 - '@vue/compiler-dom': 3.3.0-alpha.9 - '@vue/compiler-ssr': 3.3.0-alpha.9 + '@vue/compiler-core': 3.3.0-alpha.8 + '@vue/compiler-dom': 3.3.0-alpha.8 + '@vue/compiler-ssr': 3.3.0-alpha.8 '@vue/consolidate': ^0.17.3 - '@vue/reactivity-transform': 3.3.0-alpha.9 - '@vue/shared': 3.3.0-alpha.9 + '@vue/reactivity-transform': 3.3.0-alpha.8 + '@vue/shared': 3.3.0-alpha.8 estree-walker: ^2.0.2 hash-sum: ^2.0.0 lru-cache: ^5.1.1 @@ -170,8 +170,8 @@ importers: packages/compiler-ssr: specifiers: - '@vue/compiler-dom': 3.3.0-alpha.9 - '@vue/shared': 3.3.0-alpha.9 + '@vue/compiler-dom': 3.3.0-alpha.8 + '@vue/shared': 3.3.0-alpha.8 dependencies: '@vue/compiler-dom': link:../compiler-dom '@vue/shared': link:../shared @@ -184,7 +184,7 @@ importers: packages/reactivity: specifiers: - '@vue/shared': 3.3.0-alpha.9 + '@vue/shared': 3.3.0-alpha.8 dependencies: '@vue/shared': link:../shared @@ -193,8 +193,8 @@ importers: '@babel/core': ^7.21.3 '@babel/parser': ^7.20.15 '@babel/types': ^7.21.3 - '@vue/compiler-core': 3.3.0-alpha.9 - '@vue/shared': 3.3.0-alpha.9 + '@vue/compiler-core': 3.3.0-alpha.8 + '@vue/shared': 3.3.0-alpha.8 estree-walker: ^2.0.2 magic-string: ^0.30.0 dependencies: @@ -209,16 +209,16 @@ importers: packages/runtime-core: specifiers: - '@vue/reactivity': 3.3.0-alpha.9 - '@vue/shared': 3.3.0-alpha.9 + '@vue/reactivity': 3.3.0-alpha.8 + '@vue/shared': 3.3.0-alpha.8 dependencies: '@vue/reactivity': link:../reactivity '@vue/shared': link:../shared packages/runtime-dom: specifiers: - '@vue/runtime-core': 3.3.0-alpha.9 - '@vue/shared': 3.3.0-alpha.9 + '@vue/runtime-core': 3.3.0-alpha.8 + '@vue/shared': 3.3.0-alpha.8 csstype: ^3.1.1 dependencies: '@vue/runtime-core': link:../runtime-core @@ -227,16 +227,16 @@ importers: packages/runtime-test: specifiers: - '@vue/runtime-core': 3.3.0-alpha.9 - '@vue/shared': 3.3.0-alpha.9 + '@vue/runtime-core': 3.3.0-alpha.8 + '@vue/shared': 3.3.0-alpha.8 dependencies: '@vue/runtime-core': link:../runtime-core '@vue/shared': link:../shared packages/server-renderer: specifiers: - '@vue/compiler-ssr': 3.3.0-alpha.9 - '@vue/shared': 3.3.0-alpha.9 + '@vue/compiler-ssr': 3.3.0-alpha.8 + '@vue/shared': 3.3.0-alpha.8 dependencies: '@vue/compiler-ssr': link:../compiler-ssr '@vue/shared': link:../shared @@ -277,11 +277,11 @@ importers: packages/vue: specifiers: - '@vue/compiler-dom': 3.3.0-alpha.9 - '@vue/compiler-sfc': 3.3.0-alpha.9 - '@vue/runtime-dom': 3.3.0-alpha.9 - '@vue/server-renderer': 3.3.0-alpha.9 - '@vue/shared': 3.3.0-alpha.9 + '@vue/compiler-dom': 3.3.0-alpha.8 + '@vue/compiler-sfc': 3.3.0-alpha.8 + '@vue/runtime-dom': 3.3.0-alpha.8 + '@vue/server-renderer': 3.3.0-alpha.8 + '@vue/shared': 3.3.0-alpha.8 dependencies: '@vue/compiler-dom': link:../compiler-dom '@vue/compiler-sfc': link:../compiler-sfc From 6c9f058270b9be46a272a233b2ceafa25f6bc76e Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 1 Aug 2023 06:12:59 +0800 Subject: [PATCH 021/192] chore: naming --- packages/reactivity/src/computed.ts | 25 ++++++++++++------------- packages/reactivity/src/effect.ts | 15 +++++++++------ packages/reactivity/src/ref.ts | 6 +++--- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 5a92b42624e..f9750541f3c 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -38,11 +38,10 @@ export class ComputedRefImpl { public readonly [ReactiveFlags.IS_READONLY]: boolean = false public _dirty = true + public _scheduled = false + public _deferredComputed: ComputedRefImpl[] = [] public _cacheable: boolean - private _computedsToAskDirty: ComputedRefImpl[] = [] - private _triggeredAfterLastEffect = false - constructor( getter: ComputedGetter, private readonly _setter: ComputedSetter, @@ -52,12 +51,12 @@ export class ComputedRefImpl { this.effect = new ReactiveEffect(getter, _c => { if (!this._dirty) { if (_c) { - this._computedsToAskDirty.push(_c) + this._deferredComputed.push(_c) } else { this._dirty = true } - if (!this._triggeredAfterLastEffect) { - this._triggeredAfterLastEffect = true + if (!this._scheduled) { + this._scheduled = true triggerRefValue(this, this) } } @@ -70,17 +69,17 @@ export class ComputedRefImpl { get value() { // the computed ref may get wrapped by other proxies e.g. readonly() #3376 const self = toRaw(this) - if (!self._dirty && self._computedsToAskDirty.length) { + if (!self._dirty && self._deferredComputed.length) { pauseTracking() - if (self._computedsToAskDirty.length >= 2) { - self._computedsToAskDirty = self._computedsToAskDirty.sort((a, b) => { + if (self._deferredComputed.length >= 2) { + self._deferredComputed = self._deferredComputed.sort((a, b) => { const aIndex = self.effect.deps.indexOf(a.dep!) const bIndex = self.effect.deps.indexOf(b.dep!) return aIndex - bIndex }) } - for (const computedToAskDirty of self._computedsToAskDirty) { - computedToAskDirty.value + for (const deferredComputed of self._deferredComputed) { + deferredComputed.value if (self._dirty) { break } @@ -95,9 +94,9 @@ export class ComputedRefImpl { } self._value = newValue self._dirty = false - self._triggeredAfterLastEffect = false + self._scheduled = false } - self._computedsToAskDirty.length = 0 + self._deferredComputed.length = 0 return self._value } diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 1ef95b31cd7..e3c500a5e24 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -30,7 +30,10 @@ export let trackOpBit = 1 */ const maxMarkerBits = 30 -export type EffectScheduler = (computedToAskDirty: ComputedRefImpl | undefined, ...args: any[]) => any +export type EffectScheduler = ( + deferredComputed: ComputedRefImpl | undefined, + ...args: any[] +) => any export type DebuggerEvent = { effect: ReactiveEffect @@ -392,26 +395,26 @@ export function trigger( export function triggerEffects( dep: Dep | ReactiveEffect[], - computedToAskDirty: ComputedRefImpl | undefined, + deferredComputed: ComputedRefImpl | undefined, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { // spread into array for stabilization const effects = isArray(dep) ? dep : [...dep] for (const effect of effects) { if (effect.computed) { - triggerEffect(effect, computedToAskDirty, debuggerEventExtraInfo) + triggerEffect(effect, deferredComputed, debuggerEventExtraInfo) } } for (const effect of effects) { if (!effect.computed) { - triggerEffect(effect, computedToAskDirty, debuggerEventExtraInfo) + triggerEffect(effect, deferredComputed, debuggerEventExtraInfo) } } } function triggerEffect( effect: ReactiveEffect, - computedToAskDirty: ComputedRefImpl | undefined, + deferredComputed: ComputedRefImpl | undefined, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { if (effect !== activeEffect || effect.allowRecurse) { @@ -419,7 +422,7 @@ function triggerEffect( effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) } if (effect.scheduler) { - effect.scheduler(computedToAskDirty) + effect.scheduler(deferredComputed) } else { effect.run() } diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 3fc594efbf8..d01058763a8 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -55,21 +55,21 @@ export function trackRefValue(ref: RefBase) { export function triggerRefValue( ref: RefBase, - computedToAskDirty: ComputedRefImpl | undefined, + deferredComputed: ComputedRefImpl | undefined, newVal?: any ) { ref = toRaw(ref) const dep = ref.dep if (dep) { if (__DEV__) { - triggerEffects(dep, computedToAskDirty, { + triggerEffects(dep, deferredComputed, { target: ref, type: TriggerOpTypes.SET, key: 'value', newValue: newVal }) } else { - triggerEffects(dep, computedToAskDirty) + triggerEffects(dep, deferredComputed) } } } From 66bcc3e05d8c7c60afc1fbc28e913bd1de49a9bf Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 1 Aug 2023 06:15:06 +0800 Subject: [PATCH 022/192] chore: with "s" --- packages/reactivity/src/computed.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index f9750541f3c..1b953e5870d 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -39,7 +39,7 @@ export class ComputedRefImpl { public _dirty = true public _scheduled = false - public _deferredComputed: ComputedRefImpl[] = [] + public _deferredComputeds: ComputedRefImpl[] = [] public _cacheable: boolean constructor( @@ -51,7 +51,7 @@ export class ComputedRefImpl { this.effect = new ReactiveEffect(getter, _c => { if (!this._dirty) { if (_c) { - this._deferredComputed.push(_c) + this._deferredComputeds.push(_c) } else { this._dirty = true } @@ -69,16 +69,16 @@ export class ComputedRefImpl { get value() { // the computed ref may get wrapped by other proxies e.g. readonly() #3376 const self = toRaw(this) - if (!self._dirty && self._deferredComputed.length) { + if (!self._dirty && self._deferredComputeds.length) { pauseTracking() - if (self._deferredComputed.length >= 2) { - self._deferredComputed = self._deferredComputed.sort((a, b) => { + if (self._deferredComputeds.length >= 2) { + self._deferredComputeds = self._deferredComputeds.sort((a, b) => { const aIndex = self.effect.deps.indexOf(a.dep!) const bIndex = self.effect.deps.indexOf(b.dep!) return aIndex - bIndex }) } - for (const deferredComputed of self._deferredComputed) { + for (const deferredComputed of self._deferredComputeds) { deferredComputed.value if (self._dirty) { break @@ -96,7 +96,7 @@ export class ComputedRefImpl { self._dirty = false self._scheduled = false } - self._deferredComputed.length = 0 + self._deferredComputeds.length = 0 return self._value } From debddde6ee056971a9e679800af05096cae795a5 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 1 Aug 2023 06:17:49 +0800 Subject: [PATCH 023/192] chore: naming --- packages/reactivity/src/effect.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 363740ad48d..434d1c621ff 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -189,18 +189,18 @@ export function effect( } let _dirty = false - let _triggeredAfterLastEffect = false - let _computedsToAskDirty: ComputedRefImpl[] = [] + let _scheduled = false + let _deferredComputeds: ComputedRefImpl[] = [] const _effect = new ReactiveEffect(fn, _c => { if (!_dirty) { if (_c) { - _computedsToAskDirty.push(_c) + _deferredComputeds.push(_c) } else { _dirty = true } - if (!_triggeredAfterLastEffect) { - _triggeredAfterLastEffect = true + if (!_scheduled) { + _scheduled = true schedulerCallbacks.push(cb) } } @@ -217,16 +217,16 @@ export function effect( return runner function cb() { - if (!_dirty && _computedsToAskDirty.length) { + if (!_dirty && _deferredComputeds.length) { pauseTracking() - if (_computedsToAskDirty.length >= 2) { - _computedsToAskDirty = _computedsToAskDirty.sort((a, b) => { + if (_deferredComputeds.length >= 2) { + _deferredComputeds = _deferredComputeds.sort((a, b) => { const aIndex = _effect.deps.indexOf(a.dep!) const bIndex = _effect.deps.indexOf(b.dep!) return aIndex - bIndex }) } - for (const computedToAskDirty of _computedsToAskDirty) { + for (const computedToAskDirty of _deferredComputeds) { computedToAskDirty.value if (_dirty) { break @@ -238,8 +238,8 @@ export function effect( _dirty = false _effect.run() } - _computedsToAskDirty.length = 0 - _triggeredAfterLastEffect = false + _deferredComputeds.length = 0 + _scheduled = false } } From ba6688019c39d4e3dd70df35b8d86b19b0c4c821 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 1 Aug 2023 06:41:11 +0800 Subject: [PATCH 024/192] chore: undo format --- packages/compiler-sfc/src/script/importUsageCheck.ts | 6 +----- packages/runtime-core/src/components/Teleport.ts | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/compiler-sfc/src/script/importUsageCheck.ts b/packages/compiler-sfc/src/script/importUsageCheck.ts index f3c3932d829..7019dcf2312 100644 --- a/packages/compiler-sfc/src/script/importUsageCheck.ts +++ b/packages/compiler-sfc/src/script/importUsageCheck.ts @@ -63,11 +63,7 @@ function resolveTemplateUsageCheckString(sfc: SFCDescriptor) { )}` } } - if ( - prop.type === NodeTypes.ATTRIBUTE && - prop.name === 'ref' && - prop.value?.content - ) { + if (prop.type === NodeTypes.ATTRIBUTE && prop.name === 'ref' && prop.value?.content) { code += `,${prop.value.content}` } } diff --git a/packages/runtime-core/src/components/Teleport.ts b/packages/runtime-core/src/components/Teleport.ts index 19ccbc5de27..4f7d16bc7d1 100644 --- a/packages/runtime-core/src/components/Teleport.ts +++ b/packages/runtime-core/src/components/Teleport.ts @@ -400,7 +400,7 @@ function hydrateTeleport( // Force-casted public typing for h and TSX props inference export const Teleport = TeleportImpl as unknown as { __isTeleport: true - new (): { + new(): { $props: VNodeProps & TeleportProps $slots: { default(): VNode[] From cb081f6878fc65d0166820b97ff11dcd8efd729f Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 1 Aug 2023 06:49:01 +0800 Subject: [PATCH 025/192] chore: `_c` -> `deferredComputed` --- packages/reactivity/src/computed.ts | 6 +++--- packages/reactivity/src/deferredComputed.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 1b953e5870d..f00f324a59f 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -48,10 +48,10 @@ export class ComputedRefImpl { isReadonly: boolean, isSSR: boolean ) { - this.effect = new ReactiveEffect(getter, _c => { + this.effect = new ReactiveEffect(getter, deferredComputed => { if (!this._dirty) { - if (_c) { - this._deferredComputeds.push(_c) + if (deferredComputed) { + this._deferredComputeds.push(deferredComputed) } else { this._dirty = true } diff --git a/packages/reactivity/src/deferredComputed.ts b/packages/reactivity/src/deferredComputed.ts index 07b4a496bf4..527da3911e5 100644 --- a/packages/reactivity/src/deferredComputed.ts +++ b/packages/reactivity/src/deferredComputed.ts @@ -38,7 +38,7 @@ class DeferredComputedRefImpl { let compareTarget: any let hasCompareTarget = false let scheduled = false - this.effect = new ReactiveEffect(getter, (_c, computedTrigger?: boolean) => { + this.effect = new ReactiveEffect(getter, (_deferredComputed, computedTrigger?: boolean) => { if (this.dep) { if (computedTrigger) { compareTarget = this._value From 76e2e6e2c512ad1af63e22a66af46022c628e964 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 1 Aug 2023 06:50:53 +0800 Subject: [PATCH 026/192] chore: `_c` -> `deferredComputed` --- packages/reactivity/src/effect.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 434d1c621ff..da4f4cf0897 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -192,10 +192,10 @@ export function effect( let _scheduled = false let _deferredComputeds: ComputedRefImpl[] = [] - const _effect = new ReactiveEffect(fn, _c => { + const _effect = new ReactiveEffect(fn, deferredComputed => { if (!_dirty) { - if (_c) { - _deferredComputeds.push(_c) + if (deferredComputed) { + _deferredComputeds.push(deferredComputed) } else { _dirty = true } From de4e436423c2971c4469a3407e7de44435ab6848 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 1 Aug 2023 07:12:47 +0800 Subject: [PATCH 027/192] chore: expressiveness --- packages/reactivity/src/effect.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index da4f4cf0897..f21de71df02 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -456,15 +456,15 @@ export function triggerEffects( const schedulerCallbacks: (() => void)[] = [] -let tracking = false +let triggeringEffect = false function triggerEffect( effect: ReactiveEffect, deferredComputed: ComputedRefImpl | undefined, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { - let isRootEffect = !tracking - tracking = true + const isRootTrigger = !triggeringEffect + triggeringEffect = true if (effect !== activeEffect || effect.allowRecurse) { if (__DEV__ && effect.onTrigger) { effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) @@ -475,11 +475,11 @@ function triggerEffect( effect.run() } } - if (isRootEffect) { + if (isRootTrigger) { while (schedulerCallbacks.length) { schedulerCallbacks.shift()!() } - tracking = false + triggeringEffect = false } } From 6c13a3021017f9631222b91ca79b2745eca6e4f7 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 1 Aug 2023 07:22:35 +0800 Subject: [PATCH 028/192] refactor: reuse `effectTrackDepth` --- packages/reactivity/src/effect.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index f21de71df02..656a5cbeefd 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -456,15 +456,12 @@ export function triggerEffects( const schedulerCallbacks: (() => void)[] = [] -let triggeringEffect = false - function triggerEffect( effect: ReactiveEffect, deferredComputed: ComputedRefImpl | undefined, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { - const isRootTrigger = !triggeringEffect - triggeringEffect = true + const isRootTrigger = effectTrackDepth === 0 if (effect !== activeEffect || effect.allowRecurse) { if (__DEV__ && effect.onTrigger) { effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) @@ -479,7 +476,6 @@ function triggerEffect( while (schedulerCallbacks.length) { schedulerCallbacks.shift()!() } - triggeringEffect = false } } From ef2a6e3e68f16dc10fce84c40089de41272538c1 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 1 Aug 2023 07:28:54 +0800 Subject: [PATCH 029/192] refactor: remove unneeded `pauseTracking()`, `resetTracking()` --- packages/reactivity/src/effect.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 656a5cbeefd..ce3b1ede9e4 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -201,7 +201,7 @@ export function effect( } if (!_scheduled) { _scheduled = true - schedulerCallbacks.push(cb) + pendingEffectRunners.push(run) } } }) @@ -216,9 +216,8 @@ export function effect( runner.effect = _effect return runner - function cb() { + function run() { if (!_dirty && _deferredComputeds.length) { - pauseTracking() if (_deferredComputeds.length >= 2) { _deferredComputeds = _deferredComputeds.sort((a, b) => { const aIndex = _effect.deps.indexOf(a.dep!) @@ -232,7 +231,6 @@ export function effect( break } } - resetTracking() } if (_dirty) { _dirty = false @@ -454,7 +452,7 @@ export function triggerEffects( } } -const schedulerCallbacks: (() => void)[] = [] +const pendingEffectRunners: (() => void)[] = [] function triggerEffect( effect: ReactiveEffect, @@ -473,8 +471,8 @@ function triggerEffect( } } if (isRootTrigger) { - while (schedulerCallbacks.length) { - schedulerCallbacks.shift()!() + while (pendingEffectRunners.length) { + pendingEffectRunners.shift()!() } } } From 7bc106991aafb25cb00d0d8a731f7c727154e379 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 1 Aug 2023 07:47:19 +0800 Subject: [PATCH 030/192] chore: sort in one line --- packages/reactivity/src/computed.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index f00f324a59f..065248c80b0 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -72,11 +72,10 @@ export class ComputedRefImpl { if (!self._dirty && self._deferredComputeds.length) { pauseTracking() if (self._deferredComputeds.length >= 2) { - self._deferredComputeds = self._deferredComputeds.sort((a, b) => { - const aIndex = self.effect.deps.indexOf(a.dep!) - const bIndex = self.effect.deps.indexOf(b.dep!) - return aIndex - bIndex - }) + self._deferredComputeds = self._deferredComputeds.sort( + (a, b) => + self.effect.deps.indexOf(a.dep!) - self.effect.deps.indexOf(b.dep!) + ) } for (const deferredComputed of self._deferredComputeds) { deferredComputed.value From f0512d607f102a3a1ab3c088c5e8ad3cc13fae26 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 1 Aug 2023 07:48:31 +0800 Subject: [PATCH 031/192] chore: sort in one line --- packages/reactivity/src/effect.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index ce3b1ede9e4..2659f9ab6e3 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -219,11 +219,9 @@ export function effect( function run() { if (!_dirty && _deferredComputeds.length) { if (_deferredComputeds.length >= 2) { - _deferredComputeds = _deferredComputeds.sort((a, b) => { - const aIndex = _effect.deps.indexOf(a.dep!) - const bIndex = _effect.deps.indexOf(b.dep!) - return aIndex - bIndex - }) + _deferredComputeds = _deferredComputeds.sort( + (a, b) => _effect.deps.indexOf(a.dep!) - _effect.deps.indexOf(b.dep!) + ) } for (const computedToAskDirty of _deferredComputeds) { computedToAskDirty.value From 7e2ac742caac191656fb3953d2e2ccbdadf62bf4 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 1 Aug 2023 07:49:35 +0800 Subject: [PATCH 032/192] chore: naming --- packages/reactivity/src/effect.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 2659f9ab6e3..4644138efad 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -223,8 +223,8 @@ export function effect( (a, b) => _effect.deps.indexOf(a.dep!) - _effect.deps.indexOf(b.dep!) ) } - for (const computedToAskDirty of _deferredComputeds) { - computedToAskDirty.value + for (const deferredComputed of _deferredComputeds) { + deferredComputed.value if (_dirty) { break } From e98d329c9395e9991e90f0222c7fd0916f0dfa86 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 1 Aug 2023 07:59:07 +0800 Subject: [PATCH 033/192] chore: import type --- packages/reactivity/src/ref.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index d01058763a8..95b1a270091 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -16,9 +16,9 @@ import { isShallow } from './reactive' import type { ShallowReactiveMarker } from './reactive' +import type { ComputedRefImpl } from './computed' import { CollectionTypes } from './collectionHandlers' import { createDep, Dep } from './dep' -import { ComputedRefImpl } from './computed' declare const RefSymbol: unique symbol export declare const RawSymbol: unique symbol From d5595cbab49c4f2096595cd136dae2be6669ca45 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 1 Aug 2023 08:33:34 +0800 Subject: [PATCH 034/192] chore: narrow `pauseTracking()` --- packages/reactivity/src/computed.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 065248c80b0..7c53e87fd38 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -70,13 +70,13 @@ export class ComputedRefImpl { // the computed ref may get wrapped by other proxies e.g. readonly() #3376 const self = toRaw(this) if (!self._dirty && self._deferredComputeds.length) { - pauseTracking() if (self._deferredComputeds.length >= 2) { self._deferredComputeds = self._deferredComputeds.sort( (a, b) => self.effect.deps.indexOf(a.dep!) - self.effect.deps.indexOf(b.dep!) ) } + pauseTracking() for (const deferredComputed of self._deferredComputeds) { deferredComputed.value if (self._dirty) { From 44cbb5eff970d09deef9a701488c2f51d12fe918 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Mon, 14 Aug 2023 13:35:37 +0800 Subject: [PATCH 035/192] perf: cache dep indexes --- packages/reactivity/src/computed.ts | 12 +++++++----- packages/reactivity/src/effect.ts | 11 +++++++---- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 7c53e87fd38..cdf0032077b 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -41,6 +41,7 @@ export class ComputedRefImpl { public _scheduled = false public _deferredComputeds: ComputedRefImpl[] = [] public _cacheable: boolean + public _depIndexes = new Map() constructor( getter: ComputedGetter, @@ -70,12 +71,13 @@ export class ComputedRefImpl { // the computed ref may get wrapped by other proxies e.g. readonly() #3376 const self = toRaw(this) if (!self._dirty && self._deferredComputeds.length) { - if (self._deferredComputeds.length >= 2) { - self._deferredComputeds = self._deferredComputeds.sort( - (a, b) => - self.effect.deps.indexOf(a.dep!) - self.effect.deps.indexOf(b.dep!) - ) + self._depIndexes.clear() + for (const c of self._deferredComputeds) { + self._depIndexes.set(c.dep!, self.effect.deps.indexOf(c.dep!)) } + self._deferredComputeds = self._deferredComputeds.sort( + (a, b) => self._depIndexes.get(a.dep!)! - self._depIndexes.get(b.dep!)! + ) pauseTracking() for (const deferredComputed of self._deferredComputeds) { deferredComputed.value diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 4644138efad..52937a4c6bc 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -192,6 +192,7 @@ export function effect( let _scheduled = false let _deferredComputeds: ComputedRefImpl[] = [] + const _depIndexes = new Map() const _effect = new ReactiveEffect(fn, deferredComputed => { if (!_dirty) { if (deferredComputed) { @@ -218,11 +219,13 @@ export function effect( function run() { if (!_dirty && _deferredComputeds.length) { - if (_deferredComputeds.length >= 2) { - _deferredComputeds = _deferredComputeds.sort( - (a, b) => _effect.deps.indexOf(a.dep!) - _effect.deps.indexOf(b.dep!) - ) + _depIndexes.clear() + for (const c of _deferredComputeds) { + _depIndexes.set(c.dep!, _effect.deps.indexOf(c.dep!)) } + _deferredComputeds = _deferredComputeds.sort( + (a, b) => _depIndexes.get(a.dep!)! - _depIndexes.get(b.dep!)! + ) for (const deferredComputed of _deferredComputeds) { deferredComputed.value if (_dirty) { From 8a3c78605e888ed4b0d15972577d669ef1ab04e4 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Mon, 14 Aug 2023 13:44:50 +0800 Subject: [PATCH 036/192] perf: redo `length >= 2` --- packages/reactivity/src/computed.ts | 17 ++++++++++------- packages/reactivity/src/effect.ts | 14 ++++++++------ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index cdf0032077b..26733142c8e 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -40,8 +40,8 @@ export class ComputedRefImpl { public _dirty = true public _scheduled = false public _deferredComputeds: ComputedRefImpl[] = [] - public _cacheable: boolean public _depIndexes = new Map() + public _cacheable: boolean constructor( getter: ComputedGetter, @@ -71,13 +71,16 @@ export class ComputedRefImpl { // the computed ref may get wrapped by other proxies e.g. readonly() #3376 const self = toRaw(this) if (!self._dirty && self._deferredComputeds.length) { - self._depIndexes.clear() - for (const c of self._deferredComputeds) { - self._depIndexes.set(c.dep!, self.effect.deps.indexOf(c.dep!)) + if (self._deferredComputeds.length >= 2) { + self._depIndexes.clear() + for (const { dep } of self._deferredComputeds) { + self._depIndexes.set(dep!, self.effect.deps.indexOf(dep!)) + } + self._deferredComputeds = self._deferredComputeds.sort( + (a, b) => + self._depIndexes.get(a.dep!)! - self._depIndexes.get(b.dep!)! + ) } - self._deferredComputeds = self._deferredComputeds.sort( - (a, b) => self._depIndexes.get(a.dep!)! - self._depIndexes.get(b.dep!)! - ) pauseTracking() for (const deferredComputed of self._deferredComputeds) { deferredComputed.value diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 52937a4c6bc..4253974ebef 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -219,13 +219,15 @@ export function effect( function run() { if (!_dirty && _deferredComputeds.length) { - _depIndexes.clear() - for (const c of _deferredComputeds) { - _depIndexes.set(c.dep!, _effect.deps.indexOf(c.dep!)) + if (_deferredComputeds.length >= 2) { + _depIndexes.clear() + for (const { dep } of _deferredComputeds) { + _depIndexes.set(dep!, _effect.deps.indexOf(dep!)) + } + _deferredComputeds = _deferredComputeds.sort( + (a, b) => _depIndexes.get(a.dep!)! - _depIndexes.get(b.dep!)! + ) } - _deferredComputeds = _deferredComputeds.sort( - (a, b) => _depIndexes.get(a.dep!)! - _depIndexes.get(b.dep!)! - ) for (const deferredComputed of _deferredComputeds) { deferredComputed.value if (_dirty) { From 76da3275f20469fc980ba784f4878e3607d795c6 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Mon, 14 Aug 2023 13:47:10 +0800 Subject: [PATCH 037/192] chore: hoist `triggerRefValue` for readability --- packages/reactivity/src/computed.ts | 8 ++++---- packages/reactivity/src/effect.ts | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 26733142c8e..7cac02baf07 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -56,10 +56,10 @@ export class ComputedRefImpl { } else { this._dirty = true } - if (!this._scheduled) { - this._scheduled = true - triggerRefValue(this, this) - } + } + if (!this._scheduled) { + this._scheduled = true + triggerRefValue(this, this) } }) this.effect.computed = this diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 4253974ebef..1a8cd25562f 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -200,10 +200,10 @@ export function effect( } else { _dirty = true } - if (!_scheduled) { - _scheduled = true - pendingEffectRunners.push(run) - } + } + if (!_scheduled) { + _scheduled = true + pendingEffectRunners.push(run) } }) if (options) { From 9002b93b871786667a760ec860e9cdd428935fdd Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Mon, 14 Aug 2023 13:51:04 +0800 Subject: [PATCH 038/192] chore: always clear dep indexes cache to avoid memory leak --- packages/reactivity/src/computed.ts | 2 +- packages/reactivity/src/effect.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 7cac02baf07..831b8c0287b 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -72,7 +72,6 @@ export class ComputedRefImpl { const self = toRaw(this) if (!self._dirty && self._deferredComputeds.length) { if (self._deferredComputeds.length >= 2) { - self._depIndexes.clear() for (const { dep } of self._deferredComputeds) { self._depIndexes.set(dep!, self.effect.deps.indexOf(dep!)) } @@ -80,6 +79,7 @@ export class ComputedRefImpl { (a, b) => self._depIndexes.get(a.dep!)! - self._depIndexes.get(b.dep!)! ) + self._depIndexes.clear() } pauseTracking() for (const deferredComputed of self._deferredComputeds) { diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 1a8cd25562f..f5d8547765a 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -220,13 +220,13 @@ export function effect( function run() { if (!_dirty && _deferredComputeds.length) { if (_deferredComputeds.length >= 2) { - _depIndexes.clear() for (const { dep } of _deferredComputeds) { _depIndexes.set(dep!, _effect.deps.indexOf(dep!)) } _deferredComputeds = _deferredComputeds.sort( (a, b) => _depIndexes.get(a.dep!)! - _depIndexes.get(b.dep!)! ) + _depIndexes.clear() } for (const deferredComputed of _deferredComputeds) { deferredComputed.value From 0f2c388f30cdbcdb896fa140da3b3f05438b08e8 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Mon, 14 Aug 2023 13:54:12 +0800 Subject: [PATCH 039/192] chore: less type hack --- packages/reactivity/src/computed.ts | 7 +++---- packages/reactivity/src/effect.ts | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 831b8c0287b..fdd239e6360 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -40,7 +40,7 @@ export class ComputedRefImpl { public _dirty = true public _scheduled = false public _deferredComputeds: ComputedRefImpl[] = [] - public _depIndexes = new Map() + public _depIndexes = new Map() public _cacheable: boolean constructor( @@ -73,11 +73,10 @@ export class ComputedRefImpl { if (!self._dirty && self._deferredComputeds.length) { if (self._deferredComputeds.length >= 2) { for (const { dep } of self._deferredComputeds) { - self._depIndexes.set(dep!, self.effect.deps.indexOf(dep!)) + self._depIndexes.set(dep, self.effect.deps.indexOf(dep!)) } self._deferredComputeds = self._deferredComputeds.sort( - (a, b) => - self._depIndexes.get(a.dep!)! - self._depIndexes.get(b.dep!)! + (a, b) => self._depIndexes.get(a.dep)! - self._depIndexes.get(b.dep)! ) self._depIndexes.clear() } diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index f5d8547765a..829318d4f12 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -192,7 +192,7 @@ export function effect( let _scheduled = false let _deferredComputeds: ComputedRefImpl[] = [] - const _depIndexes = new Map() + const _depIndexes = new Map() const _effect = new ReactiveEffect(fn, deferredComputed => { if (!_dirty) { if (deferredComputed) { @@ -221,10 +221,10 @@ export function effect( if (!_dirty && _deferredComputeds.length) { if (_deferredComputeds.length >= 2) { for (const { dep } of _deferredComputeds) { - _depIndexes.set(dep!, _effect.deps.indexOf(dep!)) + _depIndexes.set(dep, _effect.deps.indexOf(dep!)) } _deferredComputeds = _deferredComputeds.sort( - (a, b) => _depIndexes.get(a.dep!)! - _depIndexes.get(b.dep!)! + (a, b) => _depIndexes.get(a.dep)! - _depIndexes.get(b.dep)! ) _depIndexes.clear() } From f8ff93a5d04bd9cd65c96d88abea62e4764d8fd3 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 27 Aug 2023 03:25:35 +0800 Subject: [PATCH 040/192] chore: format --- packages/reactivity/src/deferredComputed.ts | 51 +++++++++++---------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/packages/reactivity/src/deferredComputed.ts b/packages/reactivity/src/deferredComputed.ts index 527da3911e5..0830fe7b5c8 100644 --- a/packages/reactivity/src/deferredComputed.ts +++ b/packages/reactivity/src/deferredComputed.ts @@ -38,33 +38,38 @@ class DeferredComputedRefImpl { let compareTarget: any let hasCompareTarget = false let scheduled = false - this.effect = new ReactiveEffect(getter, (_deferredComputed, computedTrigger?: boolean) => { - if (this.dep) { - if (computedTrigger) { - compareTarget = this._value - hasCompareTarget = true - } else if (!scheduled) { - const valueToCompare = hasCompareTarget ? compareTarget : this._value - scheduled = true - hasCompareTarget = false - scheduler(() => { - if (this.effect.active && this._get() !== valueToCompare) { - triggerRefValue(this, undefined) + this.effect = new ReactiveEffect( + getter, + (_deferredComputed, computedTrigger?: boolean) => { + if (this.dep) { + if (computedTrigger) { + compareTarget = this._value + hasCompareTarget = true + } else if (!scheduled) { + const valueToCompare = hasCompareTarget + ? compareTarget + : this._value + scheduled = true + hasCompareTarget = false + scheduler(() => { + if (this.effect.active && this._get() !== valueToCompare) { + triggerRefValue(this, undefined) + } + scheduled = false + }) + } + // chained upstream computeds are notified synchronously to ensure + // value invalidation in case of sync access; normal effects are + // deferred to be triggered in scheduler. + for (const e of this.dep) { + if (e.computed instanceof DeferredComputedRefImpl) { + e.scheduler!(undefined, true /* computedTrigger */) } - scheduled = false - }) - } - // chained upstream computeds are notified synchronously to ensure - // value invalidation in case of sync access; normal effects are - // deferred to be triggered in scheduler. - for (const e of this.dep) { - if (e.computed instanceof DeferredComputedRefImpl) { - e.scheduler!(undefined, true /* computedTrigger */) } } + this._dirty = true } - this._dirty = true - }) + ) this.effect.computed = this as any } From 17342d50ddc16c4ba1666e81d668baf34a46be68 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 27 Aug 2023 05:20:09 +0800 Subject: [PATCH 041/192] refactor: abstract deferred computeds into ReactiveEffect --- packages/reactivity/src/computed.ts | 53 ++---------- packages/reactivity/src/deferredComputed.ts | 58 ++++++-------- packages/reactivity/src/effect.ts | 89 +++++++++++---------- 3 files changed, 80 insertions(+), 120 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index fdd239e6360..c1ae41ba48f 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -1,9 +1,4 @@ -import { - DebuggerOptions, - pauseTracking, - ReactiveEffect, - resetTracking -} from './effect' +import { DebuggerOptions, ReactiveEffect } from './effect' import { Ref, trackRefValue, triggerRefValue } from './ref' import { hasChanged, isFunction, NOOP } from '@vue/shared' import { ReactiveFlags, toRaw } from './reactive' @@ -37,10 +32,6 @@ export class ComputedRefImpl { public readonly __v_isRef = true public readonly [ReactiveFlags.IS_READONLY]: boolean = false - public _dirty = true - public _scheduled = false - public _deferredComputeds: ComputedRefImpl[] = [] - public _depIndexes = new Map() public _cacheable: boolean constructor( @@ -49,19 +40,11 @@ export class ComputedRefImpl { isReadonly: boolean, isSSR: boolean ) { - this.effect = new ReactiveEffect(getter, deferredComputed => { - if (!this._dirty) { - if (deferredComputed) { - this._deferredComputeds.push(deferredComputed) - } else { - this._dirty = true - } - } - if (!this._scheduled) { - this._scheduled = true - triggerRefValue(this, this) - } + this.effect = new ReactiveEffect(getter, () => { + this.effect._scheduled = true + triggerRefValue(this, this) }) + this.effect._dirty = true this.effect.computed = this this.effect.active = this._cacheable = !isSSR this[ReactiveFlags.IS_READONLY] = isReadonly @@ -70,36 +53,16 @@ export class ComputedRefImpl { get value() { // the computed ref may get wrapped by other proxies e.g. readonly() #3376 const self = toRaw(this) - if (!self._dirty && self._deferredComputeds.length) { - if (self._deferredComputeds.length >= 2) { - for (const { dep } of self._deferredComputeds) { - self._depIndexes.set(dep, self.effect.deps.indexOf(dep!)) - } - self._deferredComputeds = self._deferredComputeds.sort( - (a, b) => self._depIndexes.get(a.dep)! - self._depIndexes.get(b.dep)! - ) - self._depIndexes.clear() - } - pauseTracking() - for (const deferredComputed of self._deferredComputeds) { - deferredComputed.value - if (self._dirty) { - break - } - } - resetTracking() - } trackRefValue(self) - if (self._dirty || !self._cacheable) { + if (!self._cacheable || self.effect.dirty) { const newValue = self.effect.run()! if (hasChanged(self._value, newValue)) { triggerRefValue(self, undefined) } self._value = newValue - self._dirty = false - self._scheduled = false + self.effect._dirty = false + self.effect._scheduled = false } - self._deferredComputeds.length = 0 return self._value } diff --git a/packages/reactivity/src/deferredComputed.ts b/packages/reactivity/src/deferredComputed.ts index 0830fe7b5c8..4d88744b1db 100644 --- a/packages/reactivity/src/deferredComputed.ts +++ b/packages/reactivity/src/deferredComputed.ts @@ -28,7 +28,6 @@ class DeferredComputedRefImpl { public dep?: Dep = undefined private _value!: T - private _dirty = true public readonly effect: ReactiveEffect public readonly __v_isRef = true @@ -37,45 +36,40 @@ class DeferredComputedRefImpl { constructor(getter: ComputedGetter) { let compareTarget: any let hasCompareTarget = false - let scheduled = false - this.effect = new ReactiveEffect( - getter, - (_deferredComputed, computedTrigger?: boolean) => { - if (this.dep) { - if (computedTrigger) { - compareTarget = this._value - hasCompareTarget = true - } else if (!scheduled) { - const valueToCompare = hasCompareTarget - ? compareTarget - : this._value - scheduled = true - hasCompareTarget = false - scheduler(() => { - if (this.effect.active && this._get() !== valueToCompare) { - triggerRefValue(this, undefined) - } - scheduled = false - }) - } - // chained upstream computeds are notified synchronously to ensure - // value invalidation in case of sync access; normal effects are - // deferred to be triggered in scheduler. - for (const e of this.dep) { - if (e.computed instanceof DeferredComputedRefImpl) { - e.scheduler!(undefined, true /* computedTrigger */) + this.effect = new ReactiveEffect(getter, (computedTrigger?: boolean) => { + if (this.dep) { + if (computedTrigger) { + compareTarget = this._value + hasCompareTarget = true + } else if (!this.effect._scheduled) { + const valueToCompare = hasCompareTarget ? compareTarget : this._value + this.effect._scheduled = true + hasCompareTarget = false + scheduler(() => { + if (this.effect.active && this._get() !== valueToCompare) { + triggerRefValue(this, undefined) } + this.effect._scheduled = false + }) + } + // chained upstream computeds are notified synchronously to ensure + // value invalidation in case of sync access; normal effects are + // deferred to be triggered in scheduler. + for (const e of this.dep) { + if (e.computed instanceof DeferredComputedRefImpl) { + e.scheduler!(true /* computedTrigger */) } } - this._dirty = true + this.effect._dirty = true } - ) + }) + this.effect._dirty = true this.effect.computed = this as any } private _get() { - if (this._dirty) { - this._dirty = false + if (this.effect.dirty) { + this.effect._dirty = false return (this._value = this.effect.run()!) } return this._value diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 829318d4f12..38336e5fd66 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -30,10 +30,7 @@ export let trackOpBit = 1 */ const maxMarkerBits = 30 -export type EffectScheduler = ( - deferredComputed: ComputedRefImpl | undefined, - ...args: any[] -) => any +export type EffectScheduler = (...args: any[]) => any export type DebuggerEvent = { effect: ReactiveEffect @@ -78,6 +75,35 @@ export class ReactiveEffect { // dev only onTrigger?: (event: DebuggerEvent) => void + public _dirty = false + public _scheduled = false + public _deferredComputeds: ComputedRefImpl[] = [] + private _depIndexes = new Map() + + public get dirty() { + if (!this._dirty && this._deferredComputeds.length) { + if (this._deferredComputeds.length >= 2) { + for (const { dep } of this._deferredComputeds) { + this._depIndexes.set(dep, this.deps.indexOf(dep!)) + } + this._deferredComputeds = this._deferredComputeds.sort( + (a, b) => this._depIndexes.get(a.dep)! - this._depIndexes.get(b.dep)! + ) + this._depIndexes.clear() + } + pauseTracking() + for (const deferredComputed of this._deferredComputeds) { + deferredComputed.value + if (this._dirty) { + break + } + } + resetTracking() + } + this._deferredComputeds.length = 0 + return this._dirty + } + constructor( public fn: () => T, public scheduler: EffectScheduler | null = null, @@ -188,23 +214,9 @@ export function effect( fn = (fn as ReactiveEffectRunner).effect.fn } - let _dirty = false - let _scheduled = false - let _deferredComputeds: ComputedRefImpl[] = [] - - const _depIndexes = new Map() - const _effect = new ReactiveEffect(fn, deferredComputed => { - if (!_dirty) { - if (deferredComputed) { - _deferredComputeds.push(deferredComputed) - } else { - _dirty = true - } - } - if (!_scheduled) { - _scheduled = true - pendingEffectRunners.push(run) - } + const _effect = new ReactiveEffect(fn, () => { + _effect._scheduled = true + pendingEffectRunners.push(run) }) if (options) { extend(_effect, options) @@ -218,29 +230,11 @@ export function effect( return runner function run() { - if (!_dirty && _deferredComputeds.length) { - if (_deferredComputeds.length >= 2) { - for (const { dep } of _deferredComputeds) { - _depIndexes.set(dep, _effect.deps.indexOf(dep!)) - } - _deferredComputeds = _deferredComputeds.sort( - (a, b) => _depIndexes.get(a.dep)! - _depIndexes.get(b.dep)! - ) - _depIndexes.clear() - } - for (const deferredComputed of _deferredComputeds) { - deferredComputed.value - if (_dirty) { - break - } - } - } - if (_dirty) { - _dirty = false + if (_effect.dirty) { _effect.run() + _effect._dirty = false } - _deferredComputeds.length = 0 - _scheduled = false + _effect._scheduled = false } } @@ -468,7 +462,16 @@ function triggerEffect( effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) } if (effect.scheduler) { - effect.scheduler(deferredComputed) + if (!effect._dirty) { + if (deferredComputed) { + effect._deferredComputeds.push(deferredComputed) + } else { + effect._dirty = true + } + } + if (!effect._scheduled) { + effect.scheduler() + } } else { effect.run() } From 0194f0249127ee7ef004881a875e95546fc00ffb Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 27 Aug 2023 05:23:33 +0800 Subject: [PATCH 042/192] fix: fixes _dirty updating for deferredComputed --- packages/reactivity/src/deferredComputed.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reactivity/src/deferredComputed.ts b/packages/reactivity/src/deferredComputed.ts index 4d88744b1db..263d3b82502 100644 --- a/packages/reactivity/src/deferredComputed.ts +++ b/packages/reactivity/src/deferredComputed.ts @@ -60,8 +60,8 @@ class DeferredComputedRefImpl { e.scheduler!(true /* computedTrigger */) } } - this.effect._dirty = true } + this.effect._dirty = true }) this.effect._dirty = true this.effect.computed = this as any From 16dc2b88dedc9d0a997d0742b906edce1cdffbd9 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 27 Aug 2023 05:26:05 +0800 Subject: [PATCH 043/192] chore: revert deferredComputed --- packages/reactivity/src/deferredComputed.ts | 17 +++++++++-------- packages/reactivity/src/ref.ts | 7 ++----- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/packages/reactivity/src/deferredComputed.ts b/packages/reactivity/src/deferredComputed.ts index 263d3b82502..a23122046a4 100644 --- a/packages/reactivity/src/deferredComputed.ts +++ b/packages/reactivity/src/deferredComputed.ts @@ -28,6 +28,7 @@ class DeferredComputedRefImpl { public dep?: Dep = undefined private _value!: T + private _dirty = true public readonly effect: ReactiveEffect public readonly __v_isRef = true @@ -36,20 +37,21 @@ class DeferredComputedRefImpl { constructor(getter: ComputedGetter) { let compareTarget: any let hasCompareTarget = false + let scheduled = false this.effect = new ReactiveEffect(getter, (computedTrigger?: boolean) => { if (this.dep) { if (computedTrigger) { compareTarget = this._value hasCompareTarget = true - } else if (!this.effect._scheduled) { + } else if (!scheduled) { const valueToCompare = hasCompareTarget ? compareTarget : this._value - this.effect._scheduled = true + scheduled = true hasCompareTarget = false scheduler(() => { if (this.effect.active && this._get() !== valueToCompare) { - triggerRefValue(this, undefined) + triggerRefValue(this) } - this.effect._scheduled = false + scheduled = false }) } // chained upstream computeds are notified synchronously to ensure @@ -61,15 +63,14 @@ class DeferredComputedRefImpl { } } } - this.effect._dirty = true + this._dirty = true }) - this.effect._dirty = true this.effect.computed = this as any } private _get() { - if (this.effect.dirty) { - this.effect._dirty = false + if (this._dirty) { + this._dirty = false return (this._value = this.effect.run()!) } return this._value diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 448995e736d..6dd10ad503e 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -55,7 +55,7 @@ export function trackRefValue(ref: RefBase) { export function triggerRefValue( ref: RefBase, - deferredComputed: ComputedRefImpl | undefined, + deferredComputed?: ComputedRefImpl, newVal?: any ) { ref = toRaw(ref) @@ -143,10 +143,7 @@ class RefImpl { public dep?: Dep = undefined public readonly __v_isRef = true - constructor( - value: T, - public readonly __v_isShallow: boolean - ) { + constructor(value: T, public readonly __v_isShallow: boolean) { this._rawValue = __v_isShallow ? value : toRaw(value) this._value = __v_isShallow ? value : toReactive(value) } From 1b666b9c9271723b18c6b05e460b859b5fa084db Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 27 Aug 2023 05:31:34 +0800 Subject: [PATCH 044/192] chore: format --- packages/reactivity/src/ref.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 6dd10ad503e..66431acb812 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -143,7 +143,10 @@ class RefImpl { public dep?: Dep = undefined public readonly __v_isRef = true - constructor(value: T, public readonly __v_isShallow: boolean) { + constructor( + value: T, + public readonly __v_isShallow: boolean + ) { this._rawValue = __v_isShallow ? value : toRaw(value) this._value = __v_isShallow ? value : toReactive(value) } From bf89e57b83cb9b3eb77587bb53b8ffe3ea33c361 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 27 Aug 2023 05:56:26 +0800 Subject: [PATCH 045/192] perf: more efficient `watch()` --- .../runtime-core/__tests__/apiWatch.spec.ts | 25 +++++++++++++++++++ packages/runtime-core/src/apiWatch.ts | 13 +++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index f24ce80b9df..7ead1011e25 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -1205,4 +1205,29 @@ describe('api: watch', () => { expect(countWE).toBe(3) expect(countW).toBe(2) }) + + it('watch callback on-demand trigger', () => { + const effectSpy = vi.fn() + + const sec = ref(0) + const min = computed(() => { + return Math.floor(sec.value / 60) + }) + const hour = computed(() => { + return Math.floor(min.value / 60) + }) + + watchEffect( + () => { + effectSpy() + min.value + hour.value + }, + { flush: 'sync' } + ) + + for (sec.value = 0; sec.value < 1000; sec.value++) {} + + expect(effectSpy).toHaveBeenCalledTimes(17) + }) }) diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 1b85ba12d19..991c7573ffb 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -310,6 +310,11 @@ function doWatch( if (!effect.active) { return } + effect._scheduled = false + if (!effect.dirty) { + return + } + effect._dirty = false if (cb) { // watch(source, cb) const newValue = effect.run() @@ -361,7 +366,13 @@ function doWatch( scheduler = () => queueJob(job) } - const effect = new ReactiveEffect(getter, scheduler) + const effect = new ReactiveEffect(getter, () => { + effect._scheduled = true + scheduler() + }) + if (immediate) { + effect._dirty = true + } if (__DEV__) { effect.onTrack = onTrack From 09e35124dac32af39b8acd174f33dba66db17e56 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 27 Aug 2023 06:32:25 +0800 Subject: [PATCH 046/192] chore(effect): use allow function --- packages/reactivity/src/effect.ts | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 38336e5fd66..77915de50b0 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -216,7 +216,13 @@ export function effect( const _effect = new ReactiveEffect(fn, () => { _effect._scheduled = true - pendingEffectRunners.push(run) + queueEffectCbs.push(() => { + if (_effect.dirty) { + _effect.run() + _effect._dirty = false + } + _effect._scheduled = false + }) }) if (options) { extend(_effect, options) @@ -228,14 +234,6 @@ export function effect( const runner = _effect.run.bind(_effect) as ReactiveEffectRunner runner.effect = _effect return runner - - function run() { - if (_effect.dirty) { - _effect.run() - _effect._dirty = false - } - _effect._scheduled = false - } } /** @@ -449,7 +447,7 @@ export function triggerEffects( } } -const pendingEffectRunners: (() => void)[] = [] +const queueEffectCbs: (() => void)[] = [] function triggerEffect( effect: ReactiveEffect, @@ -477,8 +475,8 @@ function triggerEffect( } } if (isRootTrigger) { - while (pendingEffectRunners.length) { - pendingEffectRunners.shift()!() + while (queueEffectCbs.length) { + queueEffectCbs.shift()!() } } } From 25578fdceaf7fc9c1132358d1ebc9563cabe32c5 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 27 Aug 2023 06:35:09 +0800 Subject: [PATCH 047/192] chore(effect): release _deferredComputeds earlier --- packages/reactivity/src/effect.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 77915de50b0..a1d3ec53405 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -465,6 +465,7 @@ function triggerEffect( effect._deferredComputeds.push(deferredComputed) } else { effect._dirty = true + effect._deferredComputeds.length = 0 } } if (!effect._scheduled) { From 4b72c94d03e76f75702f146804da2a072bcb14a8 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 27 Aug 2023 07:35:02 +0800 Subject: [PATCH 048/192] refactor(effect): make `scheduler` required --- packages/reactivity/src/deferredComputed.ts | 2 +- packages/reactivity/src/effect.ts | 24 +++++++++------------ 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/packages/reactivity/src/deferredComputed.ts b/packages/reactivity/src/deferredComputed.ts index a23122046a4..16f708c2b75 100644 --- a/packages/reactivity/src/deferredComputed.ts +++ b/packages/reactivity/src/deferredComputed.ts @@ -59,7 +59,7 @@ class DeferredComputedRefImpl { // deferred to be triggered in scheduler. for (const e of this.dep) { if (e.computed instanceof DeferredComputedRefImpl) { - e.scheduler!(true /* computedTrigger */) + e.scheduler(true /* computedTrigger */) } } } diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index a1d3ec53405..04a1a24b50d 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -106,7 +106,7 @@ export class ReactiveEffect { constructor( public fn: () => T, - public scheduler: EffectScheduler | null = null, + public scheduler: EffectScheduler, scope?: EffectScope ) { recordEffectScope(this, scope) @@ -459,20 +459,16 @@ function triggerEffect( if (__DEV__ && effect.onTrigger) { effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) } - if (effect.scheduler) { - if (!effect._dirty) { - if (deferredComputed) { - effect._deferredComputeds.push(deferredComputed) - } else { - effect._dirty = true - effect._deferredComputeds.length = 0 - } - } - if (!effect._scheduled) { - effect.scheduler() + if (!effect._dirty) { + if (deferredComputed) { + effect._deferredComputeds.push(deferredComputed) + } else { + effect._dirty = true + effect._deferredComputeds.length = 0 } - } else { - effect.run() + } + if (!effect._scheduled) { + effect.scheduler() } } if (isRootTrigger) { From 0257e69abcc572390f8ce666b7ab55759c8cfd9c Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 27 Aug 2023 10:00:44 +0800 Subject: [PATCH 049/192] refactor(effect): dirty -> applyDirty() --- packages/reactivity/src/computed.ts | 10 +++---- packages/reactivity/src/effect.ts | 42 +++++++++++++-------------- packages/runtime-core/src/apiWatch.ts | 10 +++---- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index c1ae41ba48f..186e8d51266 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -41,10 +41,10 @@ export class ComputedRefImpl { isSSR: boolean ) { this.effect = new ReactiveEffect(getter, () => { - this.effect._scheduled = true + this.effect.scheduled = true triggerRefValue(this, this) }) - this.effect._dirty = true + this.effect.dirty = true this.effect.computed = this this.effect.active = this._cacheable = !isSSR this[ReactiveFlags.IS_READONLY] = isReadonly @@ -54,14 +54,14 @@ export class ComputedRefImpl { // the computed ref may get wrapped by other proxies e.g. readonly() #3376 const self = toRaw(this) trackRefValue(self) - if (!self._cacheable || self.effect.dirty) { + if (!self._cacheable || self.effect.applyDirty()) { const newValue = self.effect.run()! if (hasChanged(self._value, newValue)) { triggerRefValue(self, undefined) } self._value = newValue - self.effect._dirty = false - self.effect._scheduled = false + self.effect.dirty = false + self.effect.scheduled = false } return self._value } diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 04a1a24b50d..f9466ec5bcc 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -75,13 +75,21 @@ export class ReactiveEffect { // dev only onTrigger?: (event: DebuggerEvent) => void - public _dirty = false - public _scheduled = false + public dirty = false + public scheduled = false public _deferredComputeds: ComputedRefImpl[] = [] private _depIndexes = new Map() - public get dirty() { - if (!this._dirty && this._deferredComputeds.length) { + constructor( + public fn: () => T, + public scheduler: EffectScheduler, + scope?: EffectScope + ) { + recordEffectScope(this, scope) + } + + public applyDirty() { + if (!this.dirty && this._deferredComputeds.length) { if (this._deferredComputeds.length >= 2) { for (const { dep } of this._deferredComputeds) { this._depIndexes.set(dep, this.deps.indexOf(dep!)) @@ -94,22 +102,14 @@ export class ReactiveEffect { pauseTracking() for (const deferredComputed of this._deferredComputeds) { deferredComputed.value - if (this._dirty) { + if (this.dirty) { break } } resetTracking() } this._deferredComputeds.length = 0 - return this._dirty - } - - constructor( - public fn: () => T, - public scheduler: EffectScheduler, - scope?: EffectScope - ) { - recordEffectScope(this, scope) + return this.dirty } run() { @@ -215,13 +215,13 @@ export function effect( } const _effect = new ReactiveEffect(fn, () => { - _effect._scheduled = true + _effect.scheduled = true queueEffectCbs.push(() => { - if (_effect.dirty) { + if (_effect.applyDirty()) { _effect.run() - _effect._dirty = false + _effect.dirty = false } - _effect._scheduled = false + _effect.scheduled = false }) }) if (options) { @@ -459,15 +459,15 @@ function triggerEffect( if (__DEV__ && effect.onTrigger) { effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) } - if (!effect._dirty) { + if (!effect.dirty) { if (deferredComputed) { effect._deferredComputeds.push(deferredComputed) } else { - effect._dirty = true + effect.dirty = true effect._deferredComputeds.length = 0 } } - if (!effect._scheduled) { + if (!effect.scheduled) { effect.scheduler() } } diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 991c7573ffb..b7530d3cb31 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -310,11 +310,11 @@ function doWatch( if (!effect.active) { return } - effect._scheduled = false - if (!effect.dirty) { + effect.scheduled = false + if (!effect.applyDirty()) { return } - effect._dirty = false + effect.dirty = false if (cb) { // watch(source, cb) const newValue = effect.run() @@ -367,11 +367,11 @@ function doWatch( } const effect = new ReactiveEffect(getter, () => { - effect._scheduled = true + effect.scheduled = true scheduler() }) if (immediate) { - effect._dirty = true + effect.dirty = true } if (__DEV__) { From 37d5c031a001bd3eb608b4e6b5b82f4993ec1e49 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 27 Aug 2023 10:04:23 +0800 Subject: [PATCH 050/192] chore(effect): applyDirty() -> dirty, _dirty --- packages/reactivity/src/computed.ts | 2 +- packages/reactivity/src/effect.ts | 20 ++++++++++++-------- packages/runtime-core/src/apiWatch.ts | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 186e8d51266..fb979e85a08 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -54,7 +54,7 @@ export class ComputedRefImpl { // the computed ref may get wrapped by other proxies e.g. readonly() #3376 const self = toRaw(this) trackRefValue(self) - if (!self._cacheable || self.effect.applyDirty()) { + if (!self._cacheable || self.effect.dirty) { const newValue = self.effect.run()! if (hasChanged(self._value, newValue)) { triggerRefValue(self, undefined) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index f9466ec5bcc..c73938970b2 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -75,7 +75,7 @@ export class ReactiveEffect { // dev only onTrigger?: (event: DebuggerEvent) => void - public dirty = false + public _dirty = false public scheduled = false public _deferredComputeds: ComputedRefImpl[] = [] private _depIndexes = new Map() @@ -88,8 +88,8 @@ export class ReactiveEffect { recordEffectScope(this, scope) } - public applyDirty() { - if (!this.dirty && this._deferredComputeds.length) { + public get dirty() { + if (!this._dirty && this._deferredComputeds.length) { if (this._deferredComputeds.length >= 2) { for (const { dep } of this._deferredComputeds) { this._depIndexes.set(dep, this.deps.indexOf(dep!)) @@ -102,14 +102,18 @@ export class ReactiveEffect { pauseTracking() for (const deferredComputed of this._deferredComputeds) { deferredComputed.value - if (this.dirty) { + if (this._dirty) { break } } resetTracking() } this._deferredComputeds.length = 0 - return this.dirty + return this._dirty + } + + public set dirty(value) { + this._dirty = value } run() { @@ -217,7 +221,7 @@ export function effect( const _effect = new ReactiveEffect(fn, () => { _effect.scheduled = true queueEffectCbs.push(() => { - if (_effect.applyDirty()) { + if (_effect.dirty) { _effect.run() _effect.dirty = false } @@ -459,11 +463,11 @@ function triggerEffect( if (__DEV__ && effect.onTrigger) { effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) } - if (!effect.dirty) { + if (!effect._dirty) { if (deferredComputed) { effect._deferredComputeds.push(deferredComputed) } else { - effect.dirty = true + effect._dirty = true effect._deferredComputeds.length = 0 } } diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index b7530d3cb31..3c46ae64d3b 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -311,7 +311,7 @@ function doWatch( return } effect.scheduled = false - if (!effect.applyDirty()) { + if (!effect.dirty) { return } effect.dirty = false From 1269644006cef9cb505eb0208fa2715e3da34593 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 27 Aug 2023 20:52:21 +0800 Subject: [PATCH 051/192] chore(effect): add triggerEffectCallbacks() --- packages/reactivity/src/effect.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index c73938970b2..3890f585ddc 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -458,7 +458,6 @@ function triggerEffect( deferredComputed: ComputedRefImpl | undefined, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { - const isRootTrigger = effectTrackDepth === 0 if (effect !== activeEffect || effect.allowRecurse) { if (__DEV__ && effect.onTrigger) { effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) @@ -475,7 +474,11 @@ function triggerEffect( effect.scheduler() } } - if (isRootTrigger) { + triggerEffectCallbacks() +} + +function triggerEffectCallbacks() { + if (effectTrackDepth === 0) { while (queueEffectCbs.length) { queueEffectCbs.shift()!() } From 07da2ddf800e8200ca707343dd7c840de1cdae4a Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 27 Aug 2023 21:07:07 +0800 Subject: [PATCH 052/192] chore(effect): triggerEffectCallbacks -> scheduleEffectCallbacks --- packages/reactivity/src/effect.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 3890f585ddc..852a19f78d9 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -474,11 +474,11 @@ function triggerEffect( effect.scheduler() } } - triggerEffectCallbacks() + scheduleEffectCallbacks() } -function triggerEffectCallbacks() { if (effectTrackDepth === 0) { +function scheduleEffectCallbacks() { while (queueEffectCbs.length) { queueEffectCbs.shift()!() } From 891d937ee3e81d32c60425f80a99f5ff61ef667a Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 27 Aug 2023 21:11:53 +0800 Subject: [PATCH 053/192] chore(effect): fix syntax --- packages/reactivity/src/effect.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 852a19f78d9..349c364355f 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -477,8 +477,8 @@ function triggerEffect( scheduleEffectCallbacks() } - if (effectTrackDepth === 0) { function scheduleEffectCallbacks() { + if (effectTrackDepth === 0) { while (queueEffectCbs.length) { queueEffectCbs.shift()!() } From 3350c24b1fe53e2d71e2f52967abf09c4cd768df Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 27 Aug 2023 21:27:36 +0800 Subject: [PATCH 054/192] perf(reactivity): Array `shift()` should only trigger effect once --- .../reactivity/__tests__/reactiveArray.spec.ts | 14 ++++++++++++++ packages/reactivity/src/baseHandlers.ts | 6 +++++- packages/reactivity/src/effect.ts | 16 +++++++++++++++- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/reactivity/__tests__/reactiveArray.spec.ts b/packages/reactivity/__tests__/reactiveArray.spec.ts index 808c5aa5529..8bc7ba1433a 100644 --- a/packages/reactivity/__tests__/reactiveArray.spec.ts +++ b/packages/reactivity/__tests__/reactiveArray.spec.ts @@ -99,6 +99,20 @@ describe('reactivity/reactive/Array', () => { expect(fn).toHaveBeenCalledTimes(1) }) + test('shift on Array should only trigger dependency once', () => { + const arr = reactive([1, 2, 3]) + const fn = vi.fn() + effect(() => { + for (let i = 0; i < arr.length; i++) { + arr[i] + } + fn() + }) + expect(fn).toHaveBeenCalledTimes(1) + arr.shift() + expect(fn).toHaveBeenCalledTimes(2) + }) + test('add existing index on Array should not trigger length dependency', () => { const array = new Array(3) const observed = reactive(array) diff --git a/packages/reactivity/src/baseHandlers.ts b/packages/reactivity/src/baseHandlers.ts index 259b44a1edc..264ba36cf83 100644 --- a/packages/reactivity/src/baseHandlers.ts +++ b/packages/reactivity/src/baseHandlers.ts @@ -17,7 +17,9 @@ import { trigger, ITERATE_KEY, pauseTracking, - resetTracking + resetTracking, + pauseScheduling, + resetScheduling } from './effect' import { isObject, @@ -71,7 +73,9 @@ function createArrayInstrumentations() { ;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => { instrumentations[key] = function (this: unknown[], ...args: unknown[]) { pauseTracking() + pauseScheduling() const res = (toRaw(this) as any)[key].apply(this, args) + resetScheduling() resetTracking() return res } diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 349c364355f..2804e905941 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -250,7 +250,10 @@ export function stop(runner: ReactiveEffectRunner) { } export let shouldTrack = true +export let shouldSchedule = true + const trackStack: boolean[] = [] +const scheduleStack: boolean[] = [] /** * Temporarily pauses tracking. @@ -276,6 +279,17 @@ export function resetTracking() { shouldTrack = last === undefined ? true : last } +export function pauseScheduling() { + scheduleStack.push(shouldSchedule) + shouldSchedule = false +} + +export function resetScheduling() { + const last = scheduleStack.pop() + shouldSchedule = last === undefined ? true : last + scheduleEffectCallbacks() +} + /** * Tracks access to a reactive property. * @@ -478,7 +492,7 @@ function triggerEffect( } function scheduleEffectCallbacks() { - if (effectTrackDepth === 0) { + if (effectTrackDepth === 0 && shouldSchedule) { while (queueEffectCbs.length) { queueEffectCbs.shift()!() } From c187177e9427e308f7fe7ac41907ea2d1eb88b7f Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Mon, 28 Aug 2023 17:52:46 +0800 Subject: [PATCH 055/192] refactor(effect): de-abstract `scheduled` --- packages/reactivity/src/computed.ts | 10 +++++++--- packages/reactivity/src/effect.ts | 25 +++++++++++++------------ packages/runtime-core/src/apiWatch.ts | 15 ++++++++------- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index fb979e85a08..f4fd188179f 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -34,6 +34,8 @@ export class ComputedRefImpl { public _cacheable: boolean + private scheduled = false + constructor( getter: ComputedGetter, private readonly _setter: ComputedSetter, @@ -41,8 +43,10 @@ export class ComputedRefImpl { isSSR: boolean ) { this.effect = new ReactiveEffect(getter, () => { - this.effect.scheduled = true - triggerRefValue(this, this) + if (!this.scheduled) { + this.scheduled = true + triggerRefValue(this, this) + } }) this.effect.dirty = true this.effect.computed = this @@ -60,8 +64,8 @@ export class ComputedRefImpl { triggerRefValue(self, undefined) } self._value = newValue + self.scheduled = false self.effect.dirty = false - self.effect.scheduled = false } return self._value } diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 349c364355f..17eac5598ab 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -76,7 +76,6 @@ export class ReactiveEffect { onTrigger?: (event: DebuggerEvent) => void public _dirty = false - public scheduled = false public _deferredComputeds: ComputedRefImpl[] = [] private _depIndexes = new Map() @@ -218,15 +217,19 @@ export function effect( fn = (fn as ReactiveEffectRunner).effect.fn } + let scheduled = false + const _effect = new ReactiveEffect(fn, () => { - _effect.scheduled = true - queueEffectCbs.push(() => { - if (_effect.dirty) { - _effect.run() - _effect.dirty = false - } - _effect.scheduled = false - }) + if (!scheduled) { + scheduled = true + queueEffectCbs.push(() => { + if (_effect.dirty) { + _effect.run() + _effect.dirty = false + } + scheduled = false + }) + } }) if (options) { extend(_effect, options) @@ -470,9 +473,7 @@ function triggerEffect( effect._deferredComputeds.length = 0 } } - if (!effect.scheduled) { - effect.scheduler() - } + effect.scheduler() } scheduleEffectCallbacks() } diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 3c46ae64d3b..50ec7c310b0 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -307,11 +307,8 @@ function doWatch( ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE) : INITIAL_WATCHER_VALUE const job: SchedulerJob = () => { - if (!effect.active) { - return - } - effect.scheduled = false - if (!effect.dirty) { + scheduled = false + if (!effect.active || !effect.dirty) { return } effect.dirty = false @@ -366,9 +363,13 @@ function doWatch( scheduler = () => queueJob(job) } + let scheduled = false + const effect = new ReactiveEffect(getter, () => { - effect.scheduled = true - scheduler() + if (!scheduled) { + scheduled = true + scheduler() + } }) if (immediate) { effect.dirty = true From 8f58ea33d795888fcef0fec77cce11f7bb3f3bde Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Mon, 28 Aug 2023 18:23:17 +0800 Subject: [PATCH 056/192] chore(effect): mark dirty by default --- packages/reactivity/src/computed.ts | 1 - packages/reactivity/src/effect.ts | 3 ++- packages/runtime-core/src/apiWatch.ts | 10 ++++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index f4fd188179f..e33c23bff04 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -48,7 +48,6 @@ export class ComputedRefImpl { triggerRefValue(this, this) } }) - this.effect.dirty = true this.effect.computed = this this.effect.active = this._cacheable = !isSSR this[ReactiveFlags.IS_READONLY] = isReadonly diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 17eac5598ab..633e4f160c1 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -75,7 +75,7 @@ export class ReactiveEffect { // dev only onTrigger?: (event: DebuggerEvent) => void - public _dirty = false + public _dirty = true public _deferredComputeds: ComputedRefImpl[] = [] private _depIndexes = new Map() @@ -237,6 +237,7 @@ export function effect( } if (!options || !options.lazy) { _effect.run() + _effect.dirty = false } const runner = _effect.run.bind(_effect) as ReactiveEffectRunner runner.effect = _effect diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 50ec7c310b0..61afd9f9dee 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -371,9 +371,6 @@ function doWatch( scheduler() } }) - if (immediate) { - effect.dirty = true - } if (__DEV__) { effect.onTrack = onTrack @@ -386,14 +383,19 @@ function doWatch( job() } else { oldValue = effect.run() + effect.dirty = false } } else if (flush === 'post') { queuePostRenderEffect( - effect.run.bind(effect), + () => { + effect.run() + effect.dirty = false + }, instance && instance.suspense ) } else { effect.run() + effect.dirty = false } const unwatch = () => { From 770b2de4e72595a5788248fc24ca27b718aeb863 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Mon, 28 Aug 2023 18:30:14 +0800 Subject: [PATCH 057/192] refactor(effect): abstract `dirty` setter --- packages/reactivity/src/computed.ts | 1 - packages/reactivity/src/effect.ts | 7 +------ packages/runtime-core/src/apiWatch.ts | 8 +------- 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index e33c23bff04..f44e29b0f34 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -64,7 +64,6 @@ export class ComputedRefImpl { } self._value = newValue self.scheduled = false - self.effect.dirty = false } return self._value } diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 633e4f160c1..f20e3d3fbb1 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -111,11 +111,8 @@ export class ReactiveEffect { return this._dirty } - public set dirty(value) { - this._dirty = value - } - run() { + this._dirty = false if (!this.active) { return this.fn() } @@ -225,7 +222,6 @@ export function effect( queueEffectCbs.push(() => { if (_effect.dirty) { _effect.run() - _effect.dirty = false } scheduled = false }) @@ -237,7 +233,6 @@ export function effect( } if (!options || !options.lazy) { _effect.run() - _effect.dirty = false } const runner = _effect.run.bind(_effect) as ReactiveEffectRunner runner.effect = _effect diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 61afd9f9dee..8023fdc4331 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -311,7 +311,6 @@ function doWatch( if (!effect.active || !effect.dirty) { return } - effect.dirty = false if (cb) { // watch(source, cb) const newValue = effect.run() @@ -383,19 +382,14 @@ function doWatch( job() } else { oldValue = effect.run() - effect.dirty = false } } else if (flush === 'post') { queuePostRenderEffect( - () => { - effect.run() - effect.dirty = false - }, + effect.run.bind(effect), instance && instance.suspense ) } else { effect.run() - effect.dirty = false } const unwatch = () => { From e5e895d16a7bc1fdbab10d0644f918dd230ee37d Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Mon, 28 Aug 2023 20:21:21 +0800 Subject: [PATCH 058/192] fix(effect): revert updates for watch apis --- .../runtime-core/__tests__/apiWatch.spec.ts | 25 ------------------- packages/runtime-core/src/apiWatch.ts | 12 ++------- 2 files changed, 2 insertions(+), 35 deletions(-) diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index 7ead1011e25..f24ce80b9df 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -1205,29 +1205,4 @@ describe('api: watch', () => { expect(countWE).toBe(3) expect(countW).toBe(2) }) - - it('watch callback on-demand trigger', () => { - const effectSpy = vi.fn() - - const sec = ref(0) - const min = computed(() => { - return Math.floor(sec.value / 60) - }) - const hour = computed(() => { - return Math.floor(min.value / 60) - }) - - watchEffect( - () => { - effectSpy() - min.value - hour.value - }, - { flush: 'sync' } - ) - - for (sec.value = 0; sec.value < 1000; sec.value++) {} - - expect(effectSpy).toHaveBeenCalledTimes(17) - }) }) diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 8023fdc4331..1b85ba12d19 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -307,8 +307,7 @@ function doWatch( ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE) : INITIAL_WATCHER_VALUE const job: SchedulerJob = () => { - scheduled = false - if (!effect.active || !effect.dirty) { + if (!effect.active) { return } if (cb) { @@ -362,14 +361,7 @@ function doWatch( scheduler = () => queueJob(job) } - let scheduled = false - - const effect = new ReactiveEffect(getter, () => { - if (!scheduled) { - scheduled = true - scheduler() - } - }) + const effect = new ReactiveEffect(getter, scheduler) if (__DEV__) { effect.onTrack = onTrack From 3fc6349d4f9ca7a7d32aea0b2b687b579caf8745 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Mon, 28 Aug 2023 21:44:21 +0800 Subject: [PATCH 059/192] chore: remove unneeded change --- packages/reactivity/src/ref.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 66431acb812..91826f35600 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -290,7 +290,7 @@ class CustomRefImpl { constructor(factory: CustomRefFactory) { const { get, set } = factory( () => trackRefValue(this), - () => triggerRefValue(this, undefined) + () => triggerRefValue(this) ) this._get = get this._set = set From 382168787e63d9cbe6b5d30973044e3636d6a5c7 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Mon, 28 Aug 2023 22:15:14 +0800 Subject: [PATCH 060/192] fix(computed): polyfill `_dirty` --- packages/reactivity/src/computed.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index f44e29b0f34..6fc752f3ccc 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -71,6 +71,16 @@ export class ComputedRefImpl { set value(newValue: T) { this._setter(newValue) } + + // #region polyfill _dirty to backward compatibility for Vue <= 3.3 + get _dirty() { + return this.effect.dirty + } + + set _dirty(v) { + this.effect._dirty = v + } + // #endregion } /** From eb360e6cb76eef5178ca910ef42274bb7c420dfd Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 29 Aug 2023 03:56:23 +0800 Subject: [PATCH 061/192] perf(runtime-core): re-render on deps changed --- packages/runtime-core/src/apiAsyncComponent.ts | 1 + packages/runtime-core/src/componentPublicInstance.ts | 5 ++++- packages/runtime-core/src/components/BaseTransition.ts | 1 + packages/runtime-core/src/hmr.ts | 2 ++ packages/runtime-core/src/renderer.ts | 7 ++++++- 5 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/runtime-core/src/apiAsyncComponent.ts b/packages/runtime-core/src/apiAsyncComponent.ts index 342339042ef..0dd785a1862 100644 --- a/packages/runtime-core/src/apiAsyncComponent.ts +++ b/packages/runtime-core/src/apiAsyncComponent.ts @@ -187,6 +187,7 @@ export function defineAsyncComponent< if (instance.parent && isKeepAlive(instance.parent.vnode)) { // parent is keep-alive, force update so the loaded component's // name is taken into account + instance.parent.effect._dirty = true queueJob(instance.parent.update) } }) diff --git a/packages/runtime-core/src/componentPublicInstance.ts b/packages/runtime-core/src/componentPublicInstance.ts index dc575aafff9..01d946e4d71 100644 --- a/packages/runtime-core/src/componentPublicInstance.ts +++ b/packages/runtime-core/src/componentPublicInstance.ts @@ -269,7 +269,10 @@ export const publicPropertiesMap: PublicPropertiesMap = $root: i => getPublicInstance(i.root), $emit: i => i.emit, $options: i => (__FEATURE_OPTIONS_API__ ? resolveMergedOptions(i) : i.type), - $forceUpdate: i => i.f || (i.f = () => queueJob(i.update)), + $forceUpdate: i => i.f || (i.f = () => { + i.effect._dirty = true + queueJob(i.update) + }), $nextTick: i => i.n || (i.n = nextTick.bind(i.proxy!)), $watch: i => (__FEATURE_OPTIONS_API__ ? instanceWatch.bind(i) : NOOP) } as PublicPropertiesMap) diff --git a/packages/runtime-core/src/components/BaseTransition.ts b/packages/runtime-core/src/components/BaseTransition.ts index 9cb80b94ef0..6d03edf0936 100644 --- a/packages/runtime-core/src/components/BaseTransition.ts +++ b/packages/runtime-core/src/components/BaseTransition.ts @@ -246,6 +246,7 @@ const BaseTransitionImpl: ComponentOptions = { // #6835 // it also needs to be updated when active is undefined if (instance.update.active !== false) { + instance.effect._dirty = true instance.update() } } diff --git a/packages/runtime-core/src/hmr.ts b/packages/runtime-core/src/hmr.ts index 1ce66a3da1e..f95b2414eb4 100644 --- a/packages/runtime-core/src/hmr.ts +++ b/packages/runtime-core/src/hmr.ts @@ -93,6 +93,7 @@ function rerender(id: string, newRender?: Function) { instance.renderCache = [] // this flag forces child components with slot content to update isHmrUpdating = true + instance.effect._dirty = true instance.update() isHmrUpdating = false }) @@ -137,6 +138,7 @@ function reload(id: string, newComp: HMRComponent) { // 4. Force the parent instance to re-render. This will cause all updated // components to be unmounted and re-mounted. Queue the update so that we // don't end up forcing the same parent to re-render multiple times. + instance.parent.effect._dirty = true queueJob(instance.parent.update) } else if (instance.appContext.reload) { // root instance mounted via createApp() has a reload method diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 383e17fb0f5..0f036dade99 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -1282,6 +1282,7 @@ function baseCreateRenderer( // double updating the same child component in the same flush. invalidateJob(instance.update) // instance.update is the reactive effect. + instance.effect._dirty = true instance.update() } } else { @@ -1550,7 +1551,11 @@ function baseCreateRenderer( instance.scope // track it in component's effect scope )) - const update: SchedulerJob = (instance.update = () => effect.run()) + const update: SchedulerJob = (instance.update = () => { + if (effect.dirty) { + effect.run() + } + }) update.id = instance.uid // allowRecurse // #1801, #2043 component render effects should allow recursive updates From 128b1eb0ae3ae1086b4194e2099ac6de44ab0cff Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 29 Aug 2023 04:06:13 +0800 Subject: [PATCH 062/192] chore: format --- packages/runtime-core/src/componentPublicInstance.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/runtime-core/src/componentPublicInstance.ts b/packages/runtime-core/src/componentPublicInstance.ts index 01d946e4d71..26a2eb21def 100644 --- a/packages/runtime-core/src/componentPublicInstance.ts +++ b/packages/runtime-core/src/componentPublicInstance.ts @@ -269,10 +269,12 @@ export const publicPropertiesMap: PublicPropertiesMap = $root: i => getPublicInstance(i.root), $emit: i => i.emit, $options: i => (__FEATURE_OPTIONS_API__ ? resolveMergedOptions(i) : i.type), - $forceUpdate: i => i.f || (i.f = () => { - i.effect._dirty = true - queueJob(i.update) - }), + $forceUpdate: i => + i.f || + (i.f = () => { + i.effect._dirty = true + queueJob(i.update) + }), $nextTick: i => i.n || (i.n = nextTick.bind(i.proxy!)), $watch: i => (__FEATURE_OPTIONS_API__ ? instanceWatch.bind(i) : NOOP) } as PublicPropertiesMap) From a8f2bbafb0a1ee42f1423e41beae05fb0fde476b Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 29 Aug 2023 06:10:23 +0800 Subject: [PATCH 063/192] fix: computed causes render twice at the beginning --- packages/reactivity/src/computed.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 6fc752f3ccc..d7e4c26f4d7 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -34,6 +34,7 @@ export class ComputedRefImpl { public _cacheable: boolean + private init = false private scheduled = false constructor( @@ -45,7 +46,12 @@ export class ComputedRefImpl { this.effect = new ReactiveEffect(getter, () => { if (!this.scheduled) { this.scheduled = true - triggerRefValue(this, this) + if (!this.init) { + triggerRefValue(this, undefined) + } + else { + triggerRefValue(this, this) + } } }) this.effect.computed = this @@ -59,7 +65,10 @@ export class ComputedRefImpl { trackRefValue(self) if (!self._cacheable || self.effect.dirty) { const newValue = self.effect.run()! - if (hasChanged(self._value, newValue)) { + if (!self.init) { + self.init = true + } + else if (hasChanged(self._value, newValue)) { triggerRefValue(self, undefined) } self._value = newValue From 3aaa3af802a9d6ad6a393593303e732e23a52e94 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 29 Aug 2023 06:11:50 +0800 Subject: [PATCH 064/192] chore: format --- packages/reactivity/src/computed.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index d7e4c26f4d7..6de0cfa3d97 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -48,8 +48,7 @@ export class ComputedRefImpl { this.scheduled = true if (!this.init) { triggerRefValue(this, undefined) - } - else { + } else { triggerRefValue(this, this) } } @@ -67,8 +66,7 @@ export class ComputedRefImpl { const newValue = self.effect.run()! if (!self.init) { self.init = true - } - else if (hasChanged(self._value, newValue)) { + } else if (hasChanged(self._value, newValue)) { triggerRefValue(self, undefined) } self._value = newValue From 1be6c50e603b422b46f9dcfff3e4a217af7633b0 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 29 Aug 2023 09:04:34 +0800 Subject: [PATCH 065/192] fix: multiple computed triggers render multiple times --- packages/runtime-core/src/renderer.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 0f036dade99..5f4538a0ace 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -1544,10 +1544,17 @@ function baseCreateRenderer( } } + let scheduled = false + // create reactive effect for rendering const effect = (instance.effect = new ReactiveEffect( componentUpdateFn, - () => queueJob(update), + () => { + if (!scheduled) { + scheduled = true + queueJob(update) + } + }, instance.scope // track it in component's effect scope )) @@ -1555,6 +1562,7 @@ function baseCreateRenderer( if (effect.dirty) { effect.run() } + scheduled = false }) update.id = instance.uid // allowRecurse From d2a57068f0074d1fb40fbaea1776e03e392432f5 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 29 Aug 2023 11:24:47 +0800 Subject: [PATCH 066/192] fix: computed getter trigger effect repeatedly --- packages/reactivity/src/computed.ts | 8 +-- packages/reactivity/src/deferredComputed.ts | 60 ++++++++++++--------- packages/reactivity/src/effect.ts | 52 ++++++++++++++---- packages/reactivity/src/index.ts | 1 + packages/reactivity/src/ref.ts | 24 ++++++--- packages/runtime-core/src/renderer.ts | 11 ++-- 6 files changed, 109 insertions(+), 47 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 6de0cfa3d97..fd57976de4f 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -1,4 +1,4 @@ -import { DebuggerOptions, ReactiveEffect } from './effect' +import { DebuggerOptions, ReactiveEffect, TriggerReason } from './effect' import { Ref, trackRefValue, triggerRefValue } from './ref' import { hasChanged, isFunction, NOOP } from '@vue/shared' import { ReactiveFlags, toRaw } from './reactive' @@ -47,9 +47,9 @@ export class ComputedRefImpl { if (!this.scheduled) { this.scheduled = true if (!this.init) { - triggerRefValue(this, undefined) + triggerRefValue(this, TriggerReason.ComputedDepsUpdated, undefined) } else { - triggerRefValue(this, this) + triggerRefValue(this, TriggerReason.ValueUpdatedByGetter, this) } } }) @@ -67,7 +67,7 @@ export class ComputedRefImpl { if (!self.init) { self.init = true } else if (hasChanged(self._value, newValue)) { - triggerRefValue(self, undefined) + triggerRefValue(self, TriggerReason.ValueUpdatedByGetter, undefined) } self._value = newValue self.scheduled = false diff --git a/packages/reactivity/src/deferredComputed.ts b/packages/reactivity/src/deferredComputed.ts index 16f708c2b75..02b0073d3c8 100644 --- a/packages/reactivity/src/deferredComputed.ts +++ b/packages/reactivity/src/deferredComputed.ts @@ -1,5 +1,5 @@ import { Dep } from './dep' -import { ReactiveEffect } from './effect' +import { ReactiveEffect, TriggerReason } from './effect' import { ComputedGetter, ComputedRef } from './computed' import { ReactiveFlags, toRaw } from './reactive' import { trackRefValue, triggerRefValue } from './ref' @@ -38,33 +38,45 @@ class DeferredComputedRefImpl { let compareTarget: any let hasCompareTarget = false let scheduled = false - this.effect = new ReactiveEffect(getter, (computedTrigger?: boolean) => { - if (this.dep) { - if (computedTrigger) { - compareTarget = this._value - hasCompareTarget = true - } else if (!scheduled) { - const valueToCompare = hasCompareTarget ? compareTarget : this._value - scheduled = true - hasCompareTarget = false - scheduler(() => { - if (this.effect.active && this._get() !== valueToCompare) { - triggerRefValue(this) + this.effect = new ReactiveEffect( + getter, + (_mode, computedTrigger?: boolean) => { + if (this.dep) { + if (computedTrigger) { + compareTarget = this._value + hasCompareTarget = true + } else if (!scheduled) { + const valueToCompare = hasCompareTarget + ? compareTarget + : this._value + scheduled = true + hasCompareTarget = false + scheduler(() => { + if (this.effect.active && this._get() !== valueToCompare) { + triggerRefValue( + this, + TriggerReason.ValueUpdatedByGetter, + undefined + ) + } + scheduled = false + }) + } + // chained upstream computeds are notified synchronously to ensure + // value invalidation in case of sync access; normal effects are + // deferred to be triggered in scheduler. + for (const e of this.dep) { + if (e.computed instanceof DeferredComputedRefImpl) { + e.scheduler( + TriggerReason.ValueUpdatedByGetter, + true /* computedTrigger */ + ) } - scheduled = false - }) - } - // chained upstream computeds are notified synchronously to ensure - // value invalidation in case of sync access; normal effects are - // deferred to be triggered in scheduler. - for (const e of this.dep) { - if (e.computed instanceof DeferredComputedRefImpl) { - e.scheduler(true /* computedTrigger */) } } + this._dirty = true } - this._dirty = true - }) + ) this.effect.computed = this as any } diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index f20e3d3fbb1..b66fe35b0fd 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -30,7 +30,13 @@ export let trackOpBit = 1 */ const maxMarkerBits = 30 -export type EffectScheduler = (...args: any[]) => any +export enum TriggerReason { + ValueUpdatedBySetter = 1, + ValueUpdatedByGetter = 2, + ComputedDepsUpdated = 3 +} + +export type EffectScheduler = (reason: TriggerReason, ...args: any[]) => any export type DebuggerEvent = { effect: ReactiveEffect @@ -216,8 +222,8 @@ export function effect( let scheduled = false - const _effect = new ReactiveEffect(fn, () => { - if (!scheduled) { + const _effect = new ReactiveEffect(fn, reason => { + if (reason === TriggerReason.ValueUpdatedBySetter || !scheduled) { scheduled = true queueEffectCbs.push(() => { if (_effect.dirty) { @@ -411,9 +417,14 @@ export function trigger( if (deps.length === 1) { if (deps[0]) { if (__DEV__) { - triggerEffects(deps[0], undefined, eventInfo) + triggerEffects( + deps[0], + TriggerReason.ValueUpdatedBySetter, + undefined, + eventInfo + ) } else { - triggerEffects(deps[0], undefined) + triggerEffects(deps[0], TriggerReason.ValueUpdatedBySetter, undefined) } } } else { @@ -424,15 +435,25 @@ export function trigger( } } if (__DEV__) { - triggerEffects(createDep(effects), undefined, eventInfo) + triggerEffects( + createDep(effects), + TriggerReason.ValueUpdatedBySetter, + undefined, + eventInfo + ) } else { - triggerEffects(createDep(effects), undefined) + triggerEffects( + createDep(effects), + TriggerReason.ValueUpdatedBySetter, + undefined + ) } } } export function triggerEffects( dep: Dep | ReactiveEffect[], + triggerMode: TriggerReason, deferredComputed: ComputedRefImpl | undefined, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { @@ -440,12 +461,22 @@ export function triggerEffects( const effects = isArray(dep) ? dep : [...dep] for (const effect of effects) { if (effect.computed) { - triggerEffect(effect, deferredComputed, debuggerEventExtraInfo) + triggerEffect( + effect, + triggerMode, + deferredComputed, + debuggerEventExtraInfo + ) } } for (const effect of effects) { if (!effect.computed) { - triggerEffect(effect, deferredComputed, debuggerEventExtraInfo) + triggerEffect( + effect, + triggerMode, + deferredComputed, + debuggerEventExtraInfo + ) } } } @@ -454,6 +485,7 @@ const queueEffectCbs: (() => void)[] = [] function triggerEffect( effect: ReactiveEffect, + triggerMode: TriggerReason, deferredComputed: ComputedRefImpl | undefined, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { @@ -469,7 +501,7 @@ function triggerEffect( effect._deferredComputeds.length = 0 } } - effect.scheduler() + effect.scheduler(triggerMode) } scheduleEffectCallbacks() } diff --git a/packages/reactivity/src/index.ts b/packages/reactivity/src/index.ts index ee4da5b1935..f20605886bd 100644 --- a/packages/reactivity/src/index.ts +++ b/packages/reactivity/src/index.ts @@ -56,6 +56,7 @@ export { resetTracking, ITERATE_KEY, ReactiveEffect, + TriggerReason, type ReactiveEffectRunner, type ReactiveEffectOptions, type EffectScheduler, diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 91826f35600..62800766ef1 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -1,4 +1,5 @@ import { + TriggerReason, activeEffect, getDepFromReactive, shouldTrack, @@ -55,21 +56,22 @@ export function trackRefValue(ref: RefBase) { export function triggerRefValue( ref: RefBase, - deferredComputed?: ComputedRefImpl, + triggerMode: TriggerReason, + deferredComputed: ComputedRefImpl | undefined, newVal?: any ) { ref = toRaw(ref) const dep = ref.dep if (dep) { if (__DEV__) { - triggerEffects(dep, deferredComputed, { + triggerEffects(dep, triggerMode, deferredComputed, { target: ref, type: TriggerOpTypes.SET, key: 'value', newValue: newVal }) } else { - triggerEffects(dep, deferredComputed) + triggerEffects(dep, triggerMode, deferredComputed) } } } @@ -163,7 +165,12 @@ class RefImpl { if (hasChanged(newVal, this._rawValue)) { this._rawValue = newVal this._value = useDirectValue ? newVal : toReactive(newVal) - triggerRefValue(this, undefined, newVal) + triggerRefValue( + this, + TriggerReason.ValueUpdatedBySetter, + undefined, + newVal + ) } } } @@ -194,7 +201,12 @@ class RefImpl { * @see {@link https://vuejs.org/api/reactivity-advanced.html#triggerref} */ export function triggerRef(ref: Ref) { - triggerRefValue(ref, undefined, __DEV__ ? ref.value : void 0) + triggerRefValue( + ref, + TriggerReason.ValueUpdatedBySetter, + undefined, + __DEV__ ? ref.value : void 0 + ) } export type MaybeRef = T | Ref @@ -290,7 +302,7 @@ class CustomRefImpl { constructor(factory: CustomRefFactory) { const { get, set } = factory( () => trackRefValue(this), - () => triggerRefValue(this) + () => triggerRefValue(this, TriggerReason.ValueUpdatedBySetter, undefined) ) this._get = get this._set = set diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 5f4538a0ace..478b59db909 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -45,7 +45,12 @@ import { flushPreFlushCbs, SchedulerJob } from './scheduler' -import { pauseTracking, resetTracking, ReactiveEffect } from '@vue/reactivity' +import { + pauseTracking, + resetTracking, + ReactiveEffect, + TriggerReason +} from '@vue/reactivity' import { updateProps } from './componentProps' import { updateSlots } from './componentSlots' import { pushWarningContext, popWarningContext, warn } from './warning' @@ -1549,8 +1554,8 @@ function baseCreateRenderer( // create reactive effect for rendering const effect = (instance.effect = new ReactiveEffect( componentUpdateFn, - () => { - if (!scheduled) { + reason => { + if (reason === TriggerReason.ValueUpdatedBySetter || !scheduled) { scheduled = true queueJob(update) } From 13c9a2c638e9bb400959bb1d04df0bc592134fde Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 29 Aug 2023 12:39:34 +0800 Subject: [PATCH 067/192] fix: avoid computed side effects causing re-rendering --- packages/reactivity/src/effect.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index b66fe35b0fd..16fb39c9a4e 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -82,6 +82,7 @@ export class ReactiveEffect { onTrigger?: (event: DebuggerEvent) => void public _dirty = true + public _drityTriggerReason = TriggerReason.ValueUpdatedBySetter public _deferredComputeds: ComputedRefImpl[] = [] private _depIndexes = new Map() @@ -118,7 +119,22 @@ export class ReactiveEffect { } run() { + this.resetDirty() + const r = this._run() + if (this._drityTriggerReason !== TriggerReason.ValueUpdatedBySetter) { + this._dirty = false + this._drityTriggerReason = TriggerReason.ValueUpdatedBySetter + } + return r + } + + resetDirty() { this._dirty = false + this._drityTriggerReason = TriggerReason.ValueUpdatedBySetter + this._deferredComputeds.length = 0 + } + + _run() { if (!this.active) { return this.fn() } @@ -500,6 +516,7 @@ function triggerEffect( effect._dirty = true effect._deferredComputeds.length = 0 } + effect._drityTriggerReason = triggerMode } effect.scheduler(triggerMode) } From 53db76c2a7b8935c53375f1dce091382a82a1d4c Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 29 Aug 2023 12:54:53 +0800 Subject: [PATCH 068/192] chore: _mode -> _ --- packages/reactivity/src/deferredComputed.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reactivity/src/deferredComputed.ts b/packages/reactivity/src/deferredComputed.ts index 02b0073d3c8..8da127ce7a5 100644 --- a/packages/reactivity/src/deferredComputed.ts +++ b/packages/reactivity/src/deferredComputed.ts @@ -40,7 +40,7 @@ class DeferredComputedRefImpl { let scheduled = false this.effect = new ReactiveEffect( getter, - (_mode, computedTrigger?: boolean) => { + (_, computedTrigger?: boolean) => { if (this.dep) { if (computedTrigger) { compareTarget = this._value From e76b42c8c2797a783ff95d74e8b61bdadbc4b1bc Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 29 Aug 2023 12:56:13 +0800 Subject: [PATCH 069/192] chore: format --- packages/reactivity/src/deferredComputed.ts | 61 ++++++++++----------- 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/packages/reactivity/src/deferredComputed.ts b/packages/reactivity/src/deferredComputed.ts index 8da127ce7a5..8fe7f7dde46 100644 --- a/packages/reactivity/src/deferredComputed.ts +++ b/packages/reactivity/src/deferredComputed.ts @@ -38,45 +38,40 @@ class DeferredComputedRefImpl { let compareTarget: any let hasCompareTarget = false let scheduled = false - this.effect = new ReactiveEffect( - getter, - (_, computedTrigger?: boolean) => { - if (this.dep) { - if (computedTrigger) { - compareTarget = this._value - hasCompareTarget = true - } else if (!scheduled) { - const valueToCompare = hasCompareTarget - ? compareTarget - : this._value - scheduled = true - hasCompareTarget = false - scheduler(() => { - if (this.effect.active && this._get() !== valueToCompare) { - triggerRefValue( - this, - TriggerReason.ValueUpdatedByGetter, - undefined - ) - } - scheduled = false - }) - } - // chained upstream computeds are notified synchronously to ensure - // value invalidation in case of sync access; normal effects are - // deferred to be triggered in scheduler. - for (const e of this.dep) { - if (e.computed instanceof DeferredComputedRefImpl) { - e.scheduler( + this.effect = new ReactiveEffect(getter, (_, computedTrigger?: boolean) => { + if (this.dep) { + if (computedTrigger) { + compareTarget = this._value + hasCompareTarget = true + } else if (!scheduled) { + const valueToCompare = hasCompareTarget ? compareTarget : this._value + scheduled = true + hasCompareTarget = false + scheduler(() => { + if (this.effect.active && this._get() !== valueToCompare) { + triggerRefValue( + this, TriggerReason.ValueUpdatedByGetter, - true /* computedTrigger */ + undefined ) } + scheduled = false + }) + } + // chained upstream computeds are notified synchronously to ensure + // value invalidation in case of sync access; normal effects are + // deferred to be triggered in scheduler. + for (const e of this.dep) { + if (e.computed instanceof DeferredComputedRefImpl) { + e.scheduler( + TriggerReason.ValueUpdatedByGetter, + true /* computedTrigger */ + ) } } - this._dirty = true } - ) + this._dirty = true + }) this.effect.computed = this as any } From 35f0ddd979df69ac6f7060b5adac4463d3668ad9 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 29 Aug 2023 13:04:21 +0800 Subject: [PATCH 070/192] chore: remove computed.init --- packages/reactivity/src/computed.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 6de0cfa3d97..6fc752f3ccc 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -34,7 +34,6 @@ export class ComputedRefImpl { public _cacheable: boolean - private init = false private scheduled = false constructor( @@ -46,11 +45,7 @@ export class ComputedRefImpl { this.effect = new ReactiveEffect(getter, () => { if (!this.scheduled) { this.scheduled = true - if (!this.init) { - triggerRefValue(this, undefined) - } else { - triggerRefValue(this, this) - } + triggerRefValue(this, this) } }) this.effect.computed = this @@ -64,9 +59,7 @@ export class ComputedRefImpl { trackRefValue(self) if (!self._cacheable || self.effect.dirty) { const newValue = self.effect.run()! - if (!self.init) { - self.init = true - } else if (hasChanged(self._value, newValue)) { + if (hasChanged(self._value, newValue)) { triggerRefValue(self, undefined) } self._value = newValue From e605560cb2dc3f5e0fc35c8238a3fad0b00b4bcb Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 29 Aug 2023 13:14:31 +0800 Subject: [PATCH 071/192] refactor: TriggerReason -> TriggerType --- packages/reactivity/src/computed.ts | 6 +-- packages/reactivity/src/deferredComputed.ts | 6 +-- packages/reactivity/src/effect.ts | 42 ++++++++------------- packages/reactivity/src/index.ts | 2 +- packages/reactivity/src/ref.ts | 10 ++--- packages/runtime-core/src/renderer.ts | 6 +-- 6 files changed, 31 insertions(+), 41 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index c6817f5fec9..5371d967145 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -1,4 +1,4 @@ -import { DebuggerOptions, ReactiveEffect, TriggerReason } from './effect' +import { DebuggerOptions, ReactiveEffect, TriggerType } from './effect' import { Ref, trackRefValue, triggerRefValue } from './ref' import { hasChanged, isFunction, NOOP } from '@vue/shared' import { ReactiveFlags, toRaw } from './reactive' @@ -45,7 +45,7 @@ export class ComputedRefImpl { this.effect = new ReactiveEffect(getter, () => { if (!this.scheduled) { this.scheduled = true - triggerRefValue(this, TriggerReason.ComputedDepsUpdated, this) + triggerRefValue(this, TriggerType.SideEffect, this) } }) this.effect.computed = this @@ -60,7 +60,7 @@ export class ComputedRefImpl { if (!self._cacheable || self.effect.dirty) { const newValue = self.effect.run()! if (hasChanged(self._value, newValue)) { - triggerRefValue(self, TriggerReason.ValueUpdatedByGetter, undefined) + triggerRefValue(self, TriggerType.SideEffect, undefined) } self._value = newValue self.scheduled = false diff --git a/packages/reactivity/src/deferredComputed.ts b/packages/reactivity/src/deferredComputed.ts index 8fe7f7dde46..34c91994e82 100644 --- a/packages/reactivity/src/deferredComputed.ts +++ b/packages/reactivity/src/deferredComputed.ts @@ -1,5 +1,5 @@ import { Dep } from './dep' -import { ReactiveEffect, TriggerReason } from './effect' +import { ReactiveEffect, TriggerType } from './effect' import { ComputedGetter, ComputedRef } from './computed' import { ReactiveFlags, toRaw } from './reactive' import { trackRefValue, triggerRefValue } from './ref' @@ -51,7 +51,7 @@ class DeferredComputedRefImpl { if (this.effect.active && this._get() !== valueToCompare) { triggerRefValue( this, - TriggerReason.ValueUpdatedByGetter, + TriggerType.SideEffect, undefined ) } @@ -64,7 +64,7 @@ class DeferredComputedRefImpl { for (const e of this.dep) { if (e.computed instanceof DeferredComputedRefImpl) { e.scheduler( - TriggerReason.ValueUpdatedByGetter, + TriggerType.SideEffect, true /* computedTrigger */ ) } diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 16fb39c9a4e..a1162ca8417 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -30,13 +30,12 @@ export let trackOpBit = 1 */ const maxMarkerBits = 30 -export enum TriggerReason { - ValueUpdatedBySetter = 1, - ValueUpdatedByGetter = 2, - ComputedDepsUpdated = 3 +export enum TriggerType { + Operate = 1, + SideEffect = 2 } -export type EffectScheduler = (reason: TriggerReason, ...args: any[]) => any +export type EffectScheduler = (triggerType: TriggerType, ...args: any[]) => any export type DebuggerEvent = { effect: ReactiveEffect @@ -82,7 +81,7 @@ export class ReactiveEffect { onTrigger?: (event: DebuggerEvent) => void public _dirty = true - public _drityTriggerReason = TriggerReason.ValueUpdatedBySetter + public _drityTriggerReason = TriggerType.Operate public _deferredComputeds: ComputedRefImpl[] = [] private _depIndexes = new Map() @@ -121,16 +120,16 @@ export class ReactiveEffect { run() { this.resetDirty() const r = this._run() - if (this._drityTriggerReason !== TriggerReason.ValueUpdatedBySetter) { + if (this._drityTriggerReason !== TriggerType.Operate) { this._dirty = false - this._drityTriggerReason = TriggerReason.ValueUpdatedBySetter + this._drityTriggerReason = TriggerType.Operate } return r } resetDirty() { this._dirty = false - this._drityTriggerReason = TriggerReason.ValueUpdatedBySetter + this._drityTriggerReason = TriggerType.Operate this._deferredComputeds.length = 0 } @@ -238,8 +237,8 @@ export function effect( let scheduled = false - const _effect = new ReactiveEffect(fn, reason => { - if (reason === TriggerReason.ValueUpdatedBySetter || !scheduled) { + const _effect = new ReactiveEffect(fn, triggerType => { + if (triggerType === TriggerType.Operate || !scheduled) { scheduled = true queueEffectCbs.push(() => { if (_effect.dirty) { @@ -433,14 +432,9 @@ export function trigger( if (deps.length === 1) { if (deps[0]) { if (__DEV__) { - triggerEffects( - deps[0], - TriggerReason.ValueUpdatedBySetter, - undefined, - eventInfo - ) + triggerEffects(deps[0], TriggerType.Operate, undefined, eventInfo) } else { - triggerEffects(deps[0], TriggerReason.ValueUpdatedBySetter, undefined) + triggerEffects(deps[0], TriggerType.Operate, undefined) } } } else { @@ -453,23 +447,19 @@ export function trigger( if (__DEV__) { triggerEffects( createDep(effects), - TriggerReason.ValueUpdatedBySetter, + TriggerType.Operate, undefined, eventInfo ) } else { - triggerEffects( - createDep(effects), - TriggerReason.ValueUpdatedBySetter, - undefined - ) + triggerEffects(createDep(effects), TriggerType.Operate, undefined) } } } export function triggerEffects( dep: Dep | ReactiveEffect[], - triggerMode: TriggerReason, + triggerMode: TriggerType, deferredComputed: ComputedRefImpl | undefined, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { @@ -501,7 +491,7 @@ const queueEffectCbs: (() => void)[] = [] function triggerEffect( effect: ReactiveEffect, - triggerMode: TriggerReason, + triggerMode: TriggerType, deferredComputed: ComputedRefImpl | undefined, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { diff --git a/packages/reactivity/src/index.ts b/packages/reactivity/src/index.ts index f20605886bd..05c3ac6ab56 100644 --- a/packages/reactivity/src/index.ts +++ b/packages/reactivity/src/index.ts @@ -56,7 +56,7 @@ export { resetTracking, ITERATE_KEY, ReactiveEffect, - TriggerReason, + TriggerType, type ReactiveEffectRunner, type ReactiveEffectOptions, type EffectScheduler, diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 62800766ef1..fbf0dbd3e87 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -1,5 +1,5 @@ import { - TriggerReason, + TriggerType, activeEffect, getDepFromReactive, shouldTrack, @@ -56,7 +56,7 @@ export function trackRefValue(ref: RefBase) { export function triggerRefValue( ref: RefBase, - triggerMode: TriggerReason, + triggerMode: TriggerType, deferredComputed: ComputedRefImpl | undefined, newVal?: any ) { @@ -167,7 +167,7 @@ class RefImpl { this._value = useDirectValue ? newVal : toReactive(newVal) triggerRefValue( this, - TriggerReason.ValueUpdatedBySetter, + TriggerType.Operate, undefined, newVal ) @@ -203,7 +203,7 @@ class RefImpl { export function triggerRef(ref: Ref) { triggerRefValue( ref, - TriggerReason.ValueUpdatedBySetter, + TriggerType.Operate, undefined, __DEV__ ? ref.value : void 0 ) @@ -302,7 +302,7 @@ class CustomRefImpl { constructor(factory: CustomRefFactory) { const { get, set } = factory( () => trackRefValue(this), - () => triggerRefValue(this, TriggerReason.ValueUpdatedBySetter, undefined) + () => triggerRefValue(this, TriggerType.Operate, undefined) ) this._get = get this._set = set diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 478b59db909..144f0b5205b 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -49,7 +49,7 @@ import { pauseTracking, resetTracking, ReactiveEffect, - TriggerReason + TriggerType } from '@vue/reactivity' import { updateProps } from './componentProps' import { updateSlots } from './componentSlots' @@ -1554,8 +1554,8 @@ function baseCreateRenderer( // create reactive effect for rendering const effect = (instance.effect = new ReactiveEffect( componentUpdateFn, - reason => { - if (reason === TriggerReason.ValueUpdatedBySetter || !scheduled) { + triggerType => { + if (triggerType === TriggerType.Operate || !scheduled) { scheduled = true queueJob(update) } From fe8d996221784f38e3ae5cd5106ee4fe8f46d763 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 29 Aug 2023 13:17:22 +0800 Subject: [PATCH 072/192] chore: use resetDirty --- packages/reactivity/src/effect.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index a1162ca8417..69d00383388 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -121,8 +121,7 @@ export class ReactiveEffect { this.resetDirty() const r = this._run() if (this._drityTriggerReason !== TriggerType.Operate) { - this._dirty = false - this._drityTriggerReason = TriggerType.Operate + this.resetDirty() } return r } From 9ab4eb1e26688d18b1617e03068b3b4e06b47628 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 29 Aug 2023 13:17:56 +0800 Subject: [PATCH 073/192] chore: format --- packages/reactivity/src/deferredComputed.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/reactivity/src/deferredComputed.ts b/packages/reactivity/src/deferredComputed.ts index 34c91994e82..603c097e94a 100644 --- a/packages/reactivity/src/deferredComputed.ts +++ b/packages/reactivity/src/deferredComputed.ts @@ -49,11 +49,7 @@ class DeferredComputedRefImpl { hasCompareTarget = false scheduler(() => { if (this.effect.active && this._get() !== valueToCompare) { - triggerRefValue( - this, - TriggerType.SideEffect, - undefined - ) + triggerRefValue(this, TriggerType.SideEffect, undefined) } scheduled = false }) @@ -63,10 +59,7 @@ class DeferredComputedRefImpl { // deferred to be triggered in scheduler. for (const e of this.dep) { if (e.computed instanceof DeferredComputedRefImpl) { - e.scheduler( - TriggerType.SideEffect, - true /* computedTrigger */ - ) + e.scheduler(TriggerType.SideEffect, true /* computedTrigger */) } } } From 942be5db14e18e3c85e25f3fa42e115756480ca9 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 29 Aug 2023 13:19:46 +0800 Subject: [PATCH 074/192] chore: format --- packages/reactivity/src/ref.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index fbf0dbd3e87..eb128dd7540 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -165,12 +165,7 @@ class RefImpl { if (hasChanged(newVal, this._rawValue)) { this._rawValue = newVal this._value = useDirectValue ? newVal : toReactive(newVal) - triggerRefValue( - this, - TriggerType.Operate, - undefined, - newVal - ) + triggerRefValue(this, TriggerType.Operate, undefined, newVal) } } } From b1bd1de06071a34c966f76852381f76f8164c426 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 29 Aug 2023 13:40:48 +0800 Subject: [PATCH 075/192] fix: don't reset hard reset dirty for computed --- packages/reactivity/src/effect.ts | 21 ++++++++------------- packages/runtime-core/src/renderer.ts | 3 +++ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 69d00383388..e0c47fa5aa5 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -81,7 +81,7 @@ export class ReactiveEffect { onTrigger?: (event: DebuggerEvent) => void public _dirty = true - public _drityTriggerReason = TriggerType.Operate + public _drityTriggerType = TriggerType.Operate public _deferredComputeds: ComputedRefImpl[] = [] private _depIndexes = new Map() @@ -117,22 +117,14 @@ export class ReactiveEffect { return this._dirty } - run() { - this.resetDirty() - const r = this._run() - if (this._drityTriggerReason !== TriggerType.Operate) { - this.resetDirty() - } - return r - } - resetDirty() { this._dirty = false - this._drityTriggerReason = TriggerType.Operate + this._drityTriggerType = TriggerType.Operate this._deferredComputeds.length = 0 } - _run() { + run() { + this.resetDirty() if (!this.active) { return this.fn() } @@ -243,6 +235,9 @@ export function effect( if (_effect.dirty) { _effect.run() } + if (_effect._drityTriggerType !== TriggerType.Operate) { + _effect.resetDirty() + } scheduled = false }) } @@ -505,7 +500,7 @@ function triggerEffect( effect._dirty = true effect._deferredComputeds.length = 0 } - effect._drityTriggerReason = triggerMode + effect._drityTriggerType = triggerMode } effect.scheduler(triggerMode) } diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 144f0b5205b..d2d4eeeef8a 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -1567,6 +1567,9 @@ function baseCreateRenderer( if (effect.dirty) { effect.run() } + if (effect._drityTriggerType !== TriggerType.Operate) { + effect.resetDirty() + } scheduled = false }) update.id = instance.uid From ee05427fbe4548c16a635a2ab49906e4ca1aff15 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 29 Aug 2023 22:37:31 +0800 Subject: [PATCH 076/192] fix: computed deps update should re-trigger effects --- packages/reactivity/src/computed.ts | 4 +- packages/reactivity/src/deferredComputed.ts | 4 +- packages/reactivity/src/effect.ts | 46 ++++++++++++--------- packages/reactivity/src/ref.ts | 12 +++--- packages/runtime-core/src/renderer.ts | 9 ++-- 5 files changed, 41 insertions(+), 34 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 5371d967145..f0d1b469ad2 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -45,7 +45,7 @@ export class ComputedRefImpl { this.effect = new ReactiveEffect(getter, () => { if (!this.scheduled) { this.scheduled = true - triggerRefValue(this, TriggerType.SideEffect, this) + triggerRefValue(this, TriggerType.ComputedDepsUpdated, this) } }) this.effect.computed = this @@ -60,7 +60,7 @@ export class ComputedRefImpl { if (!self._cacheable || self.effect.dirty) { const newValue = self.effect.run()! if (hasChanged(self._value, newValue)) { - triggerRefValue(self, TriggerType.SideEffect, undefined) + triggerRefValue(self, TriggerType.ComputedValueUpdated, this) } self._value = newValue self.scheduled = false diff --git a/packages/reactivity/src/deferredComputed.ts b/packages/reactivity/src/deferredComputed.ts index 603c097e94a..f4b18372dcf 100644 --- a/packages/reactivity/src/deferredComputed.ts +++ b/packages/reactivity/src/deferredComputed.ts @@ -49,7 +49,7 @@ class DeferredComputedRefImpl { hasCompareTarget = false scheduler(() => { if (this.effect.active && this._get() !== valueToCompare) { - triggerRefValue(this, TriggerType.SideEffect, undefined) + triggerRefValue(this, TriggerType.ForceDirty, undefined) } scheduled = false }) @@ -59,7 +59,7 @@ class DeferredComputedRefImpl { // deferred to be triggered in scheduler. for (const e of this.dep) { if (e.computed instanceof DeferredComputedRefImpl) { - e.scheduler(TriggerType.SideEffect, true /* computedTrigger */) + e.scheduler(TriggerType.ForceDirty, true /* computedTrigger */) } } } diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index e0c47fa5aa5..d41e32ed440 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -31,8 +31,9 @@ export let trackOpBit = 1 const maxMarkerBits = 30 export enum TriggerType { - Operate = 1, - SideEffect = 2 + ForceDirty = 1, + ComputedDepsUpdated = 2, + ComputedValueUpdated = 3 } export type EffectScheduler = (triggerType: TriggerType, ...args: any[]) => any @@ -81,7 +82,6 @@ export class ReactiveEffect { onTrigger?: (event: DebuggerEvent) => void public _dirty = true - public _drityTriggerType = TriggerType.Operate public _deferredComputeds: ComputedRefImpl[] = [] private _depIndexes = new Map() @@ -119,7 +119,6 @@ export class ReactiveEffect { resetDirty() { this._dirty = false - this._drityTriggerType = TriggerType.Operate this._deferredComputeds.length = 0 } @@ -229,15 +228,16 @@ export function effect( let scheduled = false const _effect = new ReactiveEffect(fn, triggerType => { - if (triggerType === TriggerType.Operate || !scheduled) { + if ( + triggerType === TriggerType.ForceDirty || + triggerType === TriggerType.ComputedDepsUpdated || + !scheduled + ) { scheduled = true queueEffectCbs.push(() => { if (_effect.dirty) { _effect.run() } - if (_effect._drityTriggerType !== TriggerType.Operate) { - _effect.resetDirty() - } scheduled = false }) } @@ -426,9 +426,9 @@ export function trigger( if (deps.length === 1) { if (deps[0]) { if (__DEV__) { - triggerEffects(deps[0], TriggerType.Operate, undefined, eventInfo) + triggerEffects(deps[0], TriggerType.ForceDirty, undefined, eventInfo) } else { - triggerEffects(deps[0], TriggerType.Operate, undefined) + triggerEffects(deps[0], TriggerType.ForceDirty, undefined) } } } else { @@ -441,19 +441,19 @@ export function trigger( if (__DEV__) { triggerEffects( createDep(effects), - TriggerType.Operate, + TriggerType.ForceDirty, undefined, eventInfo ) } else { - triggerEffects(createDep(effects), TriggerType.Operate, undefined) + triggerEffects(createDep(effects), TriggerType.ForceDirty, undefined) } } } export function triggerEffects( dep: Dep | ReactiveEffect[], - triggerMode: TriggerType, + triggerType: TriggerType, deferredComputed: ComputedRefImpl | undefined, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { @@ -463,7 +463,7 @@ export function triggerEffects( if (effect.computed) { triggerEffect( effect, - triggerMode, + triggerType, deferredComputed, debuggerEventExtraInfo ) @@ -473,7 +473,7 @@ export function triggerEffects( if (!effect.computed) { triggerEffect( effect, - triggerMode, + triggerType, deferredComputed, debuggerEventExtraInfo ) @@ -485,7 +485,7 @@ const queueEffectCbs: (() => void)[] = [] function triggerEffect( effect: ReactiveEffect, - triggerMode: TriggerType, + triggerType: TriggerType, deferredComputed: ComputedRefImpl | undefined, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { @@ -494,15 +494,21 @@ function triggerEffect( effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) } if (!effect._dirty) { - if (deferredComputed) { + if (triggerType === TriggerType.ComputedDepsUpdated && deferredComputed) { effect._deferredComputeds.push(deferredComputed) - } else { + } else if ( + triggerType === TriggerType.ComputedValueUpdated && + deferredComputed && + effect._deferredComputeds.includes(deferredComputed) + ) { + effect._dirty = true + effect._deferredComputeds.length = 0 + } else if (triggerType === TriggerType.ForceDirty) { effect._dirty = true effect._deferredComputeds.length = 0 } - effect._drityTriggerType = triggerMode } - effect.scheduler(triggerMode) + effect.scheduler(triggerType) } scheduleEffectCallbacks() } diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index eb128dd7540..81215da6e36 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -56,7 +56,7 @@ export function trackRefValue(ref: RefBase) { export function triggerRefValue( ref: RefBase, - triggerMode: TriggerType, + triggerType: TriggerType, deferredComputed: ComputedRefImpl | undefined, newVal?: any ) { @@ -64,14 +64,14 @@ export function triggerRefValue( const dep = ref.dep if (dep) { if (__DEV__) { - triggerEffects(dep, triggerMode, deferredComputed, { + triggerEffects(dep, triggerType, deferredComputed, { target: ref, type: TriggerOpTypes.SET, key: 'value', newValue: newVal }) } else { - triggerEffects(dep, triggerMode, deferredComputed) + triggerEffects(dep, triggerType, deferredComputed) } } } @@ -165,7 +165,7 @@ class RefImpl { if (hasChanged(newVal, this._rawValue)) { this._rawValue = newVal this._value = useDirectValue ? newVal : toReactive(newVal) - triggerRefValue(this, TriggerType.Operate, undefined, newVal) + triggerRefValue(this, TriggerType.ForceDirty, undefined, newVal) } } } @@ -198,7 +198,7 @@ class RefImpl { export function triggerRef(ref: Ref) { triggerRefValue( ref, - TriggerType.Operate, + TriggerType.ForceDirty, undefined, __DEV__ ? ref.value : void 0 ) @@ -297,7 +297,7 @@ class CustomRefImpl { constructor(factory: CustomRefFactory) { const { get, set } = factory( () => trackRefValue(this), - () => triggerRefValue(this, TriggerType.Operate, undefined) + () => triggerRefValue(this, TriggerType.ForceDirty, undefined) ) this._get = get this._set = set diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index d2d4eeeef8a..72a78667f78 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -1555,7 +1555,11 @@ function baseCreateRenderer( const effect = (instance.effect = new ReactiveEffect( componentUpdateFn, triggerType => { - if (triggerType === TriggerType.Operate || !scheduled) { + if ( + triggerType === TriggerType.ForceDirty || + triggerType === TriggerType.ComputedDepsUpdated || + !scheduled + ) { scheduled = true queueJob(update) } @@ -1567,9 +1571,6 @@ function baseCreateRenderer( if (effect.dirty) { effect.run() } - if (effect._drityTriggerType !== TriggerType.Operate) { - effect.resetDirty() - } scheduled = false }) update.id = instance.uid From 99af87b961e79ac887236358a32cf83d57c1efb8 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 29 Aug 2023 22:44:14 +0800 Subject: [PATCH 077/192] refactor(effect): add dirty setter --- packages/reactivity/src/computed.ts | 2 +- packages/reactivity/src/effect.ts | 12 +++++------- packages/runtime-core/src/apiAsyncComponent.ts | 2 +- packages/runtime-core/src/componentPublicInstance.ts | 2 +- .../runtime-core/src/components/BaseTransition.ts | 2 +- packages/runtime-core/src/hmr.ts | 4 ++-- packages/runtime-core/src/renderer.ts | 2 +- 7 files changed, 12 insertions(+), 14 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index f0d1b469ad2..ca347e2015d 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -78,7 +78,7 @@ export class ComputedRefImpl { } set _dirty(v) { - this.effect._dirty = v + this.effect.dirty = v } // #endregion } diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index d41e32ed440..e9e9fddb846 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -117,13 +117,13 @@ export class ReactiveEffect { return this._dirty } - resetDirty() { - this._dirty = false + public set dirty(v) { + this._dirty = v this._deferredComputeds.length = 0 } run() { - this.resetDirty() + this.dirty = false if (!this.active) { return this.fn() } @@ -501,11 +501,9 @@ function triggerEffect( deferredComputed && effect._deferredComputeds.includes(deferredComputed) ) { - effect._dirty = true - effect._deferredComputeds.length = 0 + effect.dirty = true } else if (triggerType === TriggerType.ForceDirty) { - effect._dirty = true - effect._deferredComputeds.length = 0 + effect.dirty = true } } effect.scheduler(triggerType) diff --git a/packages/runtime-core/src/apiAsyncComponent.ts b/packages/runtime-core/src/apiAsyncComponent.ts index 0dd785a1862..535cb83fb5d 100644 --- a/packages/runtime-core/src/apiAsyncComponent.ts +++ b/packages/runtime-core/src/apiAsyncComponent.ts @@ -187,7 +187,7 @@ export function defineAsyncComponent< if (instance.parent && isKeepAlive(instance.parent.vnode)) { // parent is keep-alive, force update so the loaded component's // name is taken into account - instance.parent.effect._dirty = true + instance.parent.effect.dirty = true queueJob(instance.parent.update) } }) diff --git a/packages/runtime-core/src/componentPublicInstance.ts b/packages/runtime-core/src/componentPublicInstance.ts index 26a2eb21def..3d2b0dd912f 100644 --- a/packages/runtime-core/src/componentPublicInstance.ts +++ b/packages/runtime-core/src/componentPublicInstance.ts @@ -272,7 +272,7 @@ export const publicPropertiesMap: PublicPropertiesMap = $forceUpdate: i => i.f || (i.f = () => { - i.effect._dirty = true + i.effect.dirty = true queueJob(i.update) }), $nextTick: i => i.n || (i.n = nextTick.bind(i.proxy!)), diff --git a/packages/runtime-core/src/components/BaseTransition.ts b/packages/runtime-core/src/components/BaseTransition.ts index 6d03edf0936..ef0632384d6 100644 --- a/packages/runtime-core/src/components/BaseTransition.ts +++ b/packages/runtime-core/src/components/BaseTransition.ts @@ -246,7 +246,7 @@ const BaseTransitionImpl: ComponentOptions = { // #6835 // it also needs to be updated when active is undefined if (instance.update.active !== false) { - instance.effect._dirty = true + instance.effect.dirty = true instance.update() } } diff --git a/packages/runtime-core/src/hmr.ts b/packages/runtime-core/src/hmr.ts index f95b2414eb4..cdf291989bd 100644 --- a/packages/runtime-core/src/hmr.ts +++ b/packages/runtime-core/src/hmr.ts @@ -93,7 +93,7 @@ function rerender(id: string, newRender?: Function) { instance.renderCache = [] // this flag forces child components with slot content to update isHmrUpdating = true - instance.effect._dirty = true + instance.effect.dirty = true instance.update() isHmrUpdating = false }) @@ -138,7 +138,7 @@ function reload(id: string, newComp: HMRComponent) { // 4. Force the parent instance to re-render. This will cause all updated // components to be unmounted and re-mounted. Queue the update so that we // don't end up forcing the same parent to re-render multiple times. - instance.parent.effect._dirty = true + instance.parent.effect.dirty = true queueJob(instance.parent.update) } else if (instance.appContext.reload) { // root instance mounted via createApp() has a reload method diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 72a78667f78..003a8e77fa3 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -1287,7 +1287,7 @@ function baseCreateRenderer( // double updating the same child component in the same flush. invalidateJob(instance.update) // instance.update is the reactive effect. - instance.effect._dirty = true + instance.effect.dirty = true instance.update() } } else { From a181339057bc93d4b4581f01148367080348f4b7 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Wed, 30 Aug 2023 02:05:31 +0800 Subject: [PATCH 078/192] fix: computed should allow recursion dirty trigger --- packages/reactivity/src/computed.ts | 10 ++++++++-- packages/reactivity/src/effect.ts | 11 ++++++++++- packages/reactivity/src/ref.ts | 1 + 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index ca347e2015d..52e4db34ae4 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -1,4 +1,9 @@ -import { DebuggerOptions, ReactiveEffect, TriggerType } from './effect' +import { + DebuggerOptions, + DeferredComputedAcceptMode, + ReactiveEffect, + TriggerType +} from './effect' import { Ref, trackRefValue, triggerRefValue } from './ref' import { hasChanged, isFunction, NOOP } from '@vue/shared' import { ReactiveFlags, toRaw } from './reactive' @@ -48,6 +53,7 @@ export class ComputedRefImpl { triggerRefValue(this, TriggerType.ComputedDepsUpdated, this) } }) + this.effect._deferredComputedAcceptMode = DeferredComputedAcceptMode.Always this.effect.computed = this this.effect.active = this._cacheable = !isSSR this[ReactiveFlags.IS_READONLY] = isReadonly @@ -60,7 +66,7 @@ export class ComputedRefImpl { if (!self._cacheable || self.effect.dirty) { const newValue = self.effect.run()! if (hasChanged(self._value, newValue)) { - triggerRefValue(self, TriggerType.ComputedValueUpdated, this) + triggerRefValue(self, TriggerType.ComputedValueUpdated, self) } self._value = newValue self.scheduled = false diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index e9e9fddb846..2f6a46691cd 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -36,6 +36,11 @@ export enum TriggerType { ComputedValueUpdated = 3 } +export enum DeferredComputedAcceptMode { + Always = 1, + OnlyWhenQuerying = 2 +} + export type EffectScheduler = (triggerType: TriggerType, ...args: any[]) => any export type DebuggerEvent = { @@ -83,6 +88,8 @@ export class ReactiveEffect { public _dirty = true public _deferredComputeds: ComputedRefImpl[] = [] + public _deferredComputedAcceptMode = + DeferredComputedAcceptMode.OnlyWhenQuerying private _depIndexes = new Map() constructor( @@ -499,7 +506,9 @@ function triggerEffect( } else if ( triggerType === TriggerType.ComputedValueUpdated && deferredComputed && - effect._deferredComputeds.includes(deferredComputed) + (effect._deferredComputedAcceptMode === + DeferredComputedAcceptMode.Always || + effect._deferredComputeds.includes(deferredComputed)) ) { effect.dirty = true } else if (triggerType === TriggerType.ForceDirty) { diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 81215da6e36..c5ec136dfd4 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -61,6 +61,7 @@ export function triggerRefValue( newVal?: any ) { ref = toRaw(ref) + deferredComputed = toRaw(deferredComputed) const dep = ref.dep if (dep) { if (__DEV__) { From 8084d74cf059c452556fb904ca67a14733db5162 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Wed, 30 Aug 2023 19:06:52 +0800 Subject: [PATCH 079/192] fix: avoid tree shaking `deferredComputed.value` --- packages/reactivity/src/effect.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index f20e3d3fbb1..c6d01c9e200 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -50,6 +50,10 @@ export let activeEffect: ReactiveEffect | undefined export const ITERATE_KEY = Symbol(__DEV__ ? 'iterate' : '') export const MAP_KEY_ITERATE_KEY = Symbol(__DEV__ ? 'Map key iterate' : '') +function triggerComputedGetter(computed: ComputedRefImpl) { + return computed.value +} + export class ReactiveEffect { active = true deps: Dep[] = [] @@ -100,7 +104,7 @@ export class ReactiveEffect { } pauseTracking() for (const deferredComputed of this._deferredComputeds) { - deferredComputed.value + triggerComputedGetter(deferredComputed.value) // wrap with function to avoid tree shaking if (this._dirty) { break } From 194f8a8cdec2fba307a120608d200e9125a83725 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Wed, 30 Aug 2023 19:07:35 +0800 Subject: [PATCH 080/192] perf: reuse `_depIndexes` --- packages/reactivity/src/effect.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index c6d01c9e200..5fdaea9a37b 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -50,6 +50,8 @@ export let activeEffect: ReactiveEffect | undefined export const ITERATE_KEY = Symbol(__DEV__ ? 'iterate' : '') export const MAP_KEY_ITERATE_KEY = Symbol(__DEV__ ? 'Map key iterate' : '') +const _depIndexes = new Map() + function triggerComputedGetter(computed: ComputedRefImpl) { return computed.value } @@ -79,9 +81,8 @@ export class ReactiveEffect { // dev only onTrigger?: (event: DebuggerEvent) => void - public _dirty = true - public _deferredComputeds: ComputedRefImpl[] = [] - private _depIndexes = new Map() + _dirty = true + _deferredComputeds: ComputedRefImpl[] = [] constructor( public fn: () => T, @@ -95,12 +96,12 @@ export class ReactiveEffect { if (!this._dirty && this._deferredComputeds.length) { if (this._deferredComputeds.length >= 2) { for (const { dep } of this._deferredComputeds) { - this._depIndexes.set(dep, this.deps.indexOf(dep!)) + _depIndexes.set(dep, this.deps.indexOf(dep!)) } this._deferredComputeds = this._deferredComputeds.sort( - (a, b) => this._depIndexes.get(a.dep)! - this._depIndexes.get(b.dep)! + (a, b) => _depIndexes.get(a.dep)! - _depIndexes.get(b.dep)! ) - this._depIndexes.clear() + _depIndexes.clear() } pauseTracking() for (const deferredComputed of this._deferredComputeds) { From f620aa96aa72d9477aa87b53ad93f3147a1ea9cf Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Wed, 30 Aug 2023 19:11:21 +0800 Subject: [PATCH 081/192] chore: fix tests --- packages/reactivity/src/effect.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 5fdaea9a37b..a889735490b 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -105,7 +105,7 @@ export class ReactiveEffect { } pauseTracking() for (const deferredComputed of this._deferredComputeds) { - triggerComputedGetter(deferredComputed.value) // wrap with function to avoid tree shaking + triggerComputedGetter(deferredComputed) // wrap with function to avoid tree shaking if (this._dirty) { break } From 8f8f3847437a996720e83d11f358af62625af4f7 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Wed, 30 Aug 2023 19:11:53 +0800 Subject: [PATCH 082/192] Squashed commit of the following: commit f620aa96aa72d9477aa87b53ad93f3147a1ea9cf Author: Johnson Chu Date: Wed Aug 30 19:11:21 2023 +0800 chore: fix tests --- packages/reactivity/src/effect.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 7e716d110cd..2ae1bf8ec15 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -117,7 +117,7 @@ export class ReactiveEffect { } pauseTracking() for (const deferredComputed of this._deferredComputeds) { - triggerComputedGetter(deferredComputed.value) // wrap with function to avoid tree shaking + triggerComputedGetter(deferredComputed) // wrap with function to avoid tree shaking if (this._dirty) { break } From c02f650ccefbe2eed28712118b4229adcc38f1d4 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Fri, 1 Sep 2023 08:28:27 +0800 Subject: [PATCH 083/192] perf: faster computed effect spread --- packages/reactivity/src/computed.ts | 15 ++++---- packages/reactivity/src/dep.ts | 5 ++- packages/reactivity/src/effect.ts | 55 +++++++++++++---------------- packages/reactivity/src/ref.ts | 23 ++++++------ 4 files changed, 48 insertions(+), 50 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 6fc752f3ccc..12d43137050 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -33,8 +33,7 @@ export class ComputedRefImpl { public readonly [ReactiveFlags.IS_READONLY]: boolean = false public _cacheable: boolean - - private scheduled = false + public _scheduled = false constructor( getter: ComputedGetter, @@ -43,9 +42,9 @@ export class ComputedRefImpl { isSSR: boolean ) { this.effect = new ReactiveEffect(getter, () => { - if (!this.scheduled) { - this.scheduled = true - triggerRefValue(this, this) + if (!this._scheduled) { + this._scheduled = true + triggerRefValue(this, true) } }) this.effect.computed = this @@ -56,14 +55,14 @@ export class ComputedRefImpl { get value() { // the computed ref may get wrapped by other proxies e.g. readonly() #3376 const self = toRaw(this) - trackRefValue(self) + trackRefValue(self, self) if (!self._cacheable || self.effect.dirty) { const newValue = self.effect.run()! if (hasChanged(self._value, newValue)) { - triggerRefValue(self, undefined) + triggerRefValue(self, false) } self._value = newValue - self.scheduled = false + self._scheduled = false } return self._value } diff --git a/packages/reactivity/src/dep.ts b/packages/reactivity/src/dep.ts index 8677f575756..e470bc07c5c 100644 --- a/packages/reactivity/src/dep.ts +++ b/packages/reactivity/src/dep.ts @@ -1,3 +1,4 @@ +import type { ComputedRefImpl } from './computed' import { ReactiveEffect, trackOpBit } from './effect' export type Dep = Set & TrackedMarkers @@ -16,12 +17,14 @@ type TrackedMarkers = { * newTracked */ n: number + computed?: ComputedRefImpl } -export const createDep = (effects?: ReactiveEffect[]): Dep => { +export const createDep = (effects?: ReactiveEffect[], computed?: ComputedRefImpl): Dep => { const dep = new Set(effects) as Dep dep.w = 0 dep.n = 0 + dep.computed = computed return dep } diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index a889735490b..eb28a0c1814 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -9,7 +9,8 @@ import { newTracked, wasTracked } from './dep' -import { ComputedRefImpl } from './computed' +import type { ComputedRefImpl } from './computed' +import type { RefBase } from './ref' // The main WeakMap that stores {target -> key -> dep} connections. // Conceptually, it's easier to think of a dependency as a Dep class @@ -50,9 +51,7 @@ export let activeEffect: ReactiveEffect | undefined export const ITERATE_KEY = Symbol(__DEV__ ? 'iterate' : '') export const MAP_KEY_ITERATE_KEY = Symbol(__DEV__ ? 'Map key iterate' : '') -const _depIndexes = new Map() - -function triggerComputedGetter(computed: ComputedRefImpl) { +function triggerComputedGetter(computed: RefBase) { return computed.value } @@ -82,7 +81,7 @@ export class ReactiveEffect { onTrigger?: (event: DebuggerEvent) => void _dirty = true - _deferredComputeds: ComputedRefImpl[] = [] + _depsMaybeDirty = false constructor( public fn: () => T, @@ -93,31 +92,25 @@ export class ReactiveEffect { } public get dirty() { - if (!this._dirty && this._deferredComputeds.length) { - if (this._deferredComputeds.length >= 2) { - for (const { dep } of this._deferredComputeds) { - _depIndexes.set(dep, this.deps.indexOf(dep!)) - } - this._deferredComputeds = this._deferredComputeds.sort( - (a, b) => _depIndexes.get(a.dep)! - _depIndexes.get(b.dep)! - ) - _depIndexes.clear() - } + if (!this._dirty && this._depsMaybeDirty) { pauseTracking() - for (const deferredComputed of this._deferredComputeds) { - triggerComputedGetter(deferredComputed) // wrap with function to avoid tree shaking - if (this._dirty) { - break + for (const dep of this.deps) { + if (dep.computed?._scheduled) { + triggerComputedGetter(dep.computed) // wrap with function call to avoid tree shaking + if (this._dirty) { + break + } } } resetTracking() } - this._deferredComputeds.length = 0 + this._depsMaybeDirty = false return this._dirty } run() { this._dirty = false + this._depsMaybeDirty = false if (!this.active) { return this.fn() } @@ -416,9 +409,9 @@ export function trigger( if (deps.length === 1) { if (deps[0]) { if (__DEV__) { - triggerEffects(deps[0], undefined, eventInfo) + triggerEffects(deps[0], false, eventInfo) } else { - triggerEffects(deps[0], undefined) + triggerEffects(deps[0], false) } } } else { @@ -429,28 +422,28 @@ export function trigger( } } if (__DEV__) { - triggerEffects(createDep(effects), undefined, eventInfo) + triggerEffects(createDep(effects), false, eventInfo) } else { - triggerEffects(createDep(effects), undefined) + triggerEffects(createDep(effects), false) } } } export function triggerEffects( dep: Dep | ReactiveEffect[], - deferredComputed: ComputedRefImpl | undefined, + isDepMaybeDirtyTrigger: boolean, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { // spread into array for stabilization const effects = isArray(dep) ? dep : [...dep] for (const effect of effects) { if (effect.computed) { - triggerEffect(effect, deferredComputed, debuggerEventExtraInfo) + triggerEffect(effect, isDepMaybeDirtyTrigger, debuggerEventExtraInfo) } } for (const effect of effects) { if (!effect.computed) { - triggerEffect(effect, deferredComputed, debuggerEventExtraInfo) + triggerEffect(effect, isDepMaybeDirtyTrigger, debuggerEventExtraInfo) } } } @@ -459,7 +452,7 @@ const queueEffectCbs: (() => void)[] = [] function triggerEffect( effect: ReactiveEffect, - deferredComputed: ComputedRefImpl | undefined, + isDepMaybeDirtyTrigger: boolean, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { if (effect !== activeEffect || effect.allowRecurse) { @@ -467,11 +460,11 @@ function triggerEffect( effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) } if (!effect._dirty) { - if (deferredComputed) { - effect._deferredComputeds.push(deferredComputed) + if (isDepMaybeDirtyTrigger) { + effect._depsMaybeDirty = true } else { effect._dirty = true - effect._deferredComputeds.length = 0 + effect._depsMaybeDirty = false } } effect.scheduler() diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 91826f35600..f9c29bf620a 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -16,9 +16,9 @@ import { isShallow } from './reactive' import type { ShallowReactiveMarker } from './reactive' -import type { ComputedRefImpl } from './computed' import { CollectionTypes } from './collectionHandlers' import { createDep, Dep } from './dep' +import type { ComputedRefImpl } from './computed' declare const RefSymbol: unique symbol export declare const RawSymbol: unique symbol @@ -33,43 +33,46 @@ export interface Ref { [RefSymbol]: true } -type RefBase = { +export type RefBase = { dep?: Dep value: T } -export function trackRefValue(ref: RefBase) { +export function trackRefValue( + ref: RefBase, + computed?: ComputedRefImpl +) { if (shouldTrack && activeEffect) { ref = toRaw(ref) if (__DEV__) { - trackEffects(ref.dep || (ref.dep = createDep()), { + trackEffects(ref.dep || (ref.dep = createDep(undefined, computed)), { target: ref, type: TrackOpTypes.GET, key: 'value' }) } else { - trackEffects(ref.dep || (ref.dep = createDep())) + trackEffects(ref.dep || (ref.dep = createDep(undefined, computed))) } } } export function triggerRefValue( ref: RefBase, - deferredComputed?: ComputedRefImpl, + isDepMaybeDirtyTrigger: boolean = false, newVal?: any ) { ref = toRaw(ref) const dep = ref.dep if (dep) { if (__DEV__) { - triggerEffects(dep, deferredComputed, { + triggerEffects(dep, isDepMaybeDirtyTrigger, { target: ref, type: TriggerOpTypes.SET, key: 'value', newValue: newVal }) } else { - triggerEffects(dep, deferredComputed) + triggerEffects(dep, isDepMaybeDirtyTrigger) } } } @@ -163,7 +166,7 @@ class RefImpl { if (hasChanged(newVal, this._rawValue)) { this._rawValue = newVal this._value = useDirectValue ? newVal : toReactive(newVal) - triggerRefValue(this, undefined, newVal) + triggerRefValue(this, false, newVal) } } } @@ -194,7 +197,7 @@ class RefImpl { * @see {@link https://vuejs.org/api/reactivity-advanced.html#triggerref} */ export function triggerRef(ref: Ref) { - triggerRefValue(ref, undefined, __DEV__ ? ref.value : void 0) + triggerRefValue(ref, false, __DEV__ ? ref.value : void 0) } export type MaybeRef = T | Ref From e74cd43672ecf051d3dc98d6d121789ce980c614 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Fri, 1 Sep 2023 08:35:21 +0800 Subject: [PATCH 084/192] chore: format --- packages/reactivity/src/dep.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/reactivity/src/dep.ts b/packages/reactivity/src/dep.ts index e470bc07c5c..4693a0cb057 100644 --- a/packages/reactivity/src/dep.ts +++ b/packages/reactivity/src/dep.ts @@ -20,7 +20,10 @@ type TrackedMarkers = { computed?: ComputedRefImpl } -export const createDep = (effects?: ReactiveEffect[], computed?: ComputedRefImpl): Dep => { +export const createDep = ( + effects?: ReactiveEffect[], + computed?: ComputedRefImpl +): Dep => { const dep = new Set(effects) as Dep dep.w = 0 dep.n = 0 From f5b03318d3499832e149ce7174828f445b453e61 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Fri, 1 Sep 2023 08:55:42 +0800 Subject: [PATCH 085/192] refactor: remove deferredComputed arg --- packages/reactivity/src/effect.ts | 38 +++++++++---------------------- packages/reactivity/src/ref.ts | 15 ++++-------- 2 files changed, 15 insertions(+), 38 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 82f2d8f3ada..6f53fbb2d81 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -430,9 +430,9 @@ export function trigger( if (deps.length === 1) { if (deps[0]) { if (__DEV__) { - triggerEffects(deps[0], TriggerType.ForceDirty, undefined, eventInfo) + triggerEffects(TriggerType.ForceDirty, deps[0], eventInfo) } else { - triggerEffects(deps[0], TriggerType.ForceDirty, undefined) + triggerEffects(TriggerType.ForceDirty, deps[0]) } } } else { @@ -443,44 +443,28 @@ export function trigger( } } if (__DEV__) { - triggerEffects( - createDep(effects), - TriggerType.ForceDirty, - undefined, - eventInfo - ) + triggerEffects(TriggerType.ForceDirty, createDep(effects), eventInfo) } else { - triggerEffects(createDep(effects), TriggerType.ForceDirty, undefined) + triggerEffects(TriggerType.ForceDirty, createDep(effects)) } } } export function triggerEffects( - dep: Dep | ReactiveEffect[], triggerType: TriggerType, - deferredComputed: ComputedRefImpl | undefined, + dep: Dep, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { // spread into array for stabilization - const effects = isArray(dep) ? dep : [...dep] + const effects = [...dep] for (const effect of effects) { if (effect.computed) { - triggerEffect( - effect, - triggerType, - deferredComputed, - debuggerEventExtraInfo - ) + triggerEffect(triggerType, dep, effect, debuggerEventExtraInfo) } } for (const effect of effects) { if (!effect.computed) { - triggerEffect( - effect, - triggerType, - deferredComputed, - debuggerEventExtraInfo - ) + triggerEffect(triggerType, dep, effect, debuggerEventExtraInfo) } } } @@ -488,9 +472,9 @@ export function triggerEffects( const queueEffectCbs: (() => void)[] = [] function triggerEffect( - effect: ReactiveEffect, triggerType: TriggerType, - deferredComputed: ComputedRefImpl | undefined, + triggerDep: Dep, + effect: ReactiveEffect, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { if (effect !== activeEffect || effect.allowRecurse) { @@ -504,7 +488,7 @@ function triggerEffect( triggerType === TriggerType.ComputedValueUpdated && (effect._deferredComputedAcceptMode === DeferredComputedAcceptMode.Always || - effect.deps.some(dep => dep.computed === deferredComputed)) + effect.deps.includes(triggerDep)) ) { effect.dirty = true } else if (triggerType === TriggerType.ForceDirty) { diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 30ca5ac1ae5..2055e26293c 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -60,22 +60,20 @@ export function trackRefValue( export function triggerRefValue( ref: RefBase, triggerType: TriggerType, - deferredComputed: ComputedRefImpl | undefined, newVal?: any ) { ref = toRaw(ref) - deferredComputed = toRaw(deferredComputed) const dep = ref.dep if (dep) { if (__DEV__) { - triggerEffects(dep, triggerType, deferredComputed, { + triggerEffects(triggerType, dep, { target: ref, type: TriggerOpTypes.SET, key: 'value', newValue: newVal }) } else { - triggerEffects(dep, triggerType, deferredComputed) + triggerEffects(triggerType, dep) } } } @@ -169,7 +167,7 @@ class RefImpl { if (hasChanged(newVal, this._rawValue)) { this._rawValue = newVal this._value = useDirectValue ? newVal : toReactive(newVal) - triggerRefValue(this, TriggerType.ForceDirty, undefined, newVal) + triggerRefValue(this, TriggerType.ForceDirty, newVal) } } } @@ -200,12 +198,7 @@ class RefImpl { * @see {@link https://vuejs.org/api/reactivity-advanced.html#triggerref} */ export function triggerRef(ref: Ref) { - triggerRefValue( - ref, - TriggerType.ForceDirty, - undefined, - __DEV__ ? ref.value : void 0 - ) + triggerRefValue(ref, TriggerType.ForceDirty, __DEV__ ? ref.value : void 0) } export type MaybeRef = T | Ref From 9e642190a46e42151e9300d86b53b6708b0407d0 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Fri, 1 Sep 2023 09:04:36 +0800 Subject: [PATCH 086/192] fix: fix vant test --- packages/reactivity/src/computed.ts | 9 ++------- packages/reactivity/src/effect.ts | 14 +++++--------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index c5a353c34ff..16bedf55bd2 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -1,9 +1,4 @@ -import { - DebuggerOptions, - DeferredComputedAcceptMode, - ReactiveEffect, - TriggerType -} from './effect' +import { DebuggerOptions, ReactiveEffect, TriggerType } from './effect' import { Ref, trackRefValue, triggerRefValue } from './ref' import { hasChanged, isFunction, NOOP } from '@vue/shared' import { ReactiveFlags, toRaw } from './reactive' @@ -52,7 +47,7 @@ export class ComputedRefImpl { triggerRefValue(this, TriggerType.ComputedDepsUpdated, this) } }) - this.effect._deferredComputedAcceptMode = DeferredComputedAcceptMode.Always + this.effect._alwaysAcceptComputedValueUpdated = true this.effect.computed = this this.effect.active = this._cacheable = !isSSR this[ReactiveFlags.IS_READONLY] = isReadonly diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 6f53fbb2d81..9aa9d112f2a 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -37,11 +37,6 @@ export enum TriggerType { ComputedValueUpdated = 3 } -export enum DeferredComputedAcceptMode { - Always = 1, - OnlyWhenQuerying = 2 -} - export type EffectScheduler = (triggerType: TriggerType, ...args: any[]) => any export type DebuggerEvent = { @@ -93,7 +88,7 @@ export class ReactiveEffect { _dirty = true _depsMaybeDirty = false - _deferredComputedAcceptMode = DeferredComputedAcceptMode.OnlyWhenQuerying + _alwaysAcceptComputedValueUpdated = false constructor( public fn: () => T, @@ -486,9 +481,10 @@ function triggerEffect( effect._depsMaybeDirty = true } else if ( triggerType === TriggerType.ComputedValueUpdated && - (effect._deferredComputedAcceptMode === - DeferredComputedAcceptMode.Always || - effect.deps.includes(triggerDep)) + (effect._alwaysAcceptComputedValueUpdated || + (effect._depsMaybeDirty && + triggerDep.computed?._scheduled && + effect.deps.includes(triggerDep))) ) { effect.dirty = true } else if (triggerType === TriggerType.ForceDirty) { From b43c4946777ea19423ad0ec31df6288eefae8c40 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Fri, 1 Sep 2023 09:24:15 +0800 Subject: [PATCH 087/192] chore: fix triggerRefValue newValue for dev --- packages/reactivity/src/computed.ts | 4 ++-- packages/reactivity/src/ref.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 16bedf55bd2..f62d87f444a 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -44,7 +44,7 @@ export class ComputedRefImpl { this.effect = new ReactiveEffect(getter, () => { if (!this._scheduled) { this._scheduled = true - triggerRefValue(this, TriggerType.ComputedDepsUpdated, this) + triggerRefValue(this, TriggerType.ComputedDepsUpdated) } }) this.effect._alwaysAcceptComputedValueUpdated = true @@ -60,7 +60,7 @@ export class ComputedRefImpl { if (!self._cacheable || self.effect.dirty) { const newValue = self.effect.run()! if (hasChanged(self._value, newValue)) { - triggerRefValue(self, TriggerType.ComputedValueUpdated, self) + triggerRefValue(self, TriggerType.ComputedValueUpdated) } self._value = newValue self._scheduled = false diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 2055e26293c..a0feb5e4ee1 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -294,7 +294,7 @@ class CustomRefImpl { constructor(factory: CustomRefFactory) { const { get, set } = factory( () => trackRefValue(this), - () => triggerRefValue(this, TriggerType.ForceDirty, undefined) + () => triggerRefValue(this, TriggerType.ForceDirty) ) this._get = get this._set = set From c522871fdc3603a152cd3e58528f4c1a45ff7679 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Fri, 1 Sep 2023 09:45:54 +0800 Subject: [PATCH 088/192] chore: remove _alwaysAcceptComputedValueUpdated --- packages/reactivity/src/computed.ts | 1 - packages/reactivity/src/effect.ts | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index f62d87f444a..38d69977ed6 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -47,7 +47,6 @@ export class ComputedRefImpl { triggerRefValue(this, TriggerType.ComputedDepsUpdated) } }) - this.effect._alwaysAcceptComputedValueUpdated = true this.effect.computed = this this.effect.active = this._cacheable = !isSSR this[ReactiveFlags.IS_READONLY] = isReadonly diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 9aa9d112f2a..9a7ab4a1116 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -88,7 +88,6 @@ export class ReactiveEffect { _dirty = true _depsMaybeDirty = false - _alwaysAcceptComputedValueUpdated = false constructor( public fn: () => T, @@ -481,7 +480,7 @@ function triggerEffect( effect._depsMaybeDirty = true } else if ( triggerType === TriggerType.ComputedValueUpdated && - (effect._alwaysAcceptComputedValueUpdated || + (effect.computed || (effect._depsMaybeDirty && triggerDep.computed?._scheduled && effect.deps.includes(triggerDep))) From 5bd18efd6c5ea02fed88a5b687ed5be42b732cbb Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 2 Sep 2023 05:05:07 +0800 Subject: [PATCH 089/192] chore: less changes --- packages/reactivity/src/effect.ts | 3 +-- packages/reactivity/src/ref.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 9a7ab4a1116..64e21283d60 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -10,7 +10,6 @@ import { wasTracked } from './dep' import type { ComputedRefImpl } from './computed' -import type { RefBase } from './ref' // The main WeakMap that stores {target -> key -> dep} connections. // Conceptually, it's easier to think of a dependency as a Dep class @@ -57,7 +56,7 @@ export let activeEffect: ReactiveEffect | undefined export const ITERATE_KEY = Symbol(__DEV__ ? 'iterate' : '') export const MAP_KEY_ITERATE_KEY = Symbol(__DEV__ ? 'Map key iterate' : '') -function triggerComputedGetter(computed: RefBase) { +function triggerComputedGetter(computed: ComputedRefImpl) { return computed.value } diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index a0feb5e4ee1..d5bf0f6da49 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -34,7 +34,7 @@ export interface Ref { [RefSymbol]: true } -export type RefBase = { +type RefBase = { dep?: Dep value: T } From b8c0a72bc8cb1a496e5455f15bc97be29e3de988 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 2 Sep 2023 05:08:47 +0800 Subject: [PATCH 090/192] chore: less changes --- packages/reactivity/src/deferredComputed.ts | 2 +- packages/reactivity/src/ref.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/reactivity/src/deferredComputed.ts b/packages/reactivity/src/deferredComputed.ts index f4b18372dcf..67893cd36ab 100644 --- a/packages/reactivity/src/deferredComputed.ts +++ b/packages/reactivity/src/deferredComputed.ts @@ -49,7 +49,7 @@ class DeferredComputedRefImpl { hasCompareTarget = false scheduler(() => { if (this.effect.active && this._get() !== valueToCompare) { - triggerRefValue(this, TriggerType.ForceDirty, undefined) + triggerRefValue(this) } scheduled = false }) diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index d5bf0f6da49..c38b777c738 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -59,7 +59,7 @@ export function trackRefValue( export function triggerRefValue( ref: RefBase, - triggerType: TriggerType, + triggerType: TriggerType = TriggerType.ForceDirty, newVal?: any ) { ref = toRaw(ref) @@ -294,7 +294,7 @@ class CustomRefImpl { constructor(factory: CustomRefFactory) { const { get, set } = factory( () => trackRefValue(this), - () => triggerRefValue(this, TriggerType.ForceDirty) + () => triggerRefValue(this) ) this._get = get this._set = set From 857f60e4beecb4a52190dfe862a8b6fc54ca3b94 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 2 Sep 2023 05:36:43 +0800 Subject: [PATCH 091/192] chore: remove unneeded `deps.includes` --- packages/reactivity/src/computed.ts | 4 +++- packages/reactivity/src/effect.ts | 7 +++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 38d69977ed6..203139971f1 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -32,8 +32,9 @@ export class ComputedRefImpl { public readonly __v_isRef = true public readonly [ReactiveFlags.IS_READONLY]: boolean = false - public _cacheable: boolean public _scheduled = false + public _valueMaybeDirty = false + public _cacheable: boolean constructor( getter: ComputedGetter, @@ -44,6 +45,7 @@ export class ComputedRefImpl { this.effect = new ReactiveEffect(getter, () => { if (!this._scheduled) { this._scheduled = true + this._valueMaybeDirty = true triggerRefValue(this, TriggerType.ComputedDepsUpdated) } }) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 64e21283d60..9c63f0276f5 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -100,8 +100,9 @@ export class ReactiveEffect { if (!this._dirty && this._depsMaybeDirty) { pauseTracking() for (const dep of this.deps) { - if (dep.computed?._scheduled) { + if (dep.computed?._valueMaybeDirty) { triggerComputedGetter(dep.computed) // wrap with function call to avoid tree shaking + dep.computed._valueMaybeDirty = false if (this._dirty) { break } @@ -480,9 +481,7 @@ function triggerEffect( } else if ( triggerType === TriggerType.ComputedValueUpdated && (effect.computed || - (effect._depsMaybeDirty && - triggerDep.computed?._scheduled && - effect.deps.includes(triggerDep))) + (effect._depsMaybeDirty && triggerDep.computed?._valueMaybeDirty)) ) { effect.dirty = true } else if (triggerType === TriggerType.ForceDirty) { From 174626c64c951aae6652fa3e4a30774d6ea8b2ff Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 2 Sep 2023 05:41:10 +0800 Subject: [PATCH 092/192] refactor: reduce `dep` arg for triggerEffect() --- packages/reactivity/src/effect.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 9c63f0276f5..35e8cce56a8 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -453,12 +453,12 @@ export function triggerEffects( const effects = [...dep] for (const effect of effects) { if (effect.computed) { - triggerEffect(triggerType, dep, effect, debuggerEventExtraInfo) + triggerEffect(triggerType, effect, debuggerEventExtraInfo) } } for (const effect of effects) { if (!effect.computed) { - triggerEffect(triggerType, dep, effect, debuggerEventExtraInfo) + triggerEffect(triggerType, effect, debuggerEventExtraInfo) } } } @@ -467,7 +467,6 @@ const queueEffectCbs: (() => void)[] = [] function triggerEffect( triggerType: TriggerType, - triggerDep: Dep, effect: ReactiveEffect, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { @@ -480,8 +479,7 @@ function triggerEffect( effect._depsMaybeDirty = true } else if ( triggerType === TriggerType.ComputedValueUpdated && - (effect.computed || - (effect._depsMaybeDirty && triggerDep.computed?._valueMaybeDirty)) + (effect.computed || effect._depsMaybeDirty) ) { effect.dirty = true } else if (triggerType === TriggerType.ForceDirty) { From d73ba7ab7aca5599832c974d61f2376972ef3f12 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 2 Sep 2023 05:49:17 +0800 Subject: [PATCH 093/192] chore: move `_valueMaybeDirty = false` --- packages/reactivity/src/effect.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 35e8cce56a8..6dcec3d6245 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -101,8 +101,8 @@ export class ReactiveEffect { pauseTracking() for (const dep of this.deps) { if (dep.computed?._valueMaybeDirty) { - triggerComputedGetter(dep.computed) // wrap with function call to avoid tree shaking dep.computed._valueMaybeDirty = false + triggerComputedGetter(dep.computed) // wrap with function call to avoid tree shaking if (this._dirty) { break } From 80ea2507673b00f640567b64328c6fcef73e235c Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 2 Sep 2023 06:25:29 +0800 Subject: [PATCH 094/192] fix: remove `_valueMaybeDirty` for fix vuetify test --- packages/reactivity/src/computed.ts | 2 -- packages/reactivity/src/effect.ts | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 203139971f1..3354b36aaeb 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -33,7 +33,6 @@ export class ComputedRefImpl { public readonly [ReactiveFlags.IS_READONLY]: boolean = false public _scheduled = false - public _valueMaybeDirty = false public _cacheable: boolean constructor( @@ -45,7 +44,6 @@ export class ComputedRefImpl { this.effect = new ReactiveEffect(getter, () => { if (!this._scheduled) { this._scheduled = true - this._valueMaybeDirty = true triggerRefValue(this, TriggerType.ComputedDepsUpdated) } }) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 6dcec3d6245..6466cc72849 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -100,8 +100,7 @@ export class ReactiveEffect { if (!this._dirty && this._depsMaybeDirty) { pauseTracking() for (const dep of this.deps) { - if (dep.computed?._valueMaybeDirty) { - dep.computed._valueMaybeDirty = false + if (dep.computed?._scheduled) { triggerComputedGetter(dep.computed) // wrap with function call to avoid tree shaking if (this._dirty) { break From e339def29c63d698ad8adf36f5ace2de960b2152 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 2 Sep 2023 07:05:38 +0800 Subject: [PATCH 095/192] refactor: abstract triggerType --- packages/reactivity/src/computed.ts | 8 +-- packages/reactivity/src/deferredComputed.ts | 6 +-- packages/reactivity/src/effect.ts | 55 ++++++++------------- packages/reactivity/src/index.ts | 1 - packages/reactivity/src/operations.ts | 6 +++ packages/reactivity/src/ref.ts | 9 ++-- packages/runtime-core/src/renderer.ts | 21 +------- 7 files changed, 41 insertions(+), 65 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 3354b36aaeb..aab3098fe30 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -1,8 +1,9 @@ -import { DebuggerOptions, ReactiveEffect, TriggerType } from './effect' +import { DebuggerOptions, ReactiveEffect } from './effect' import { Ref, trackRefValue, triggerRefValue } from './ref' import { hasChanged, isFunction, NOOP } from '@vue/shared' import { ReactiveFlags, toRaw } from './reactive' import { Dep } from './dep' +import { TriggerTypes } from './operations' declare const ComputedRefSymbol: unique symbol @@ -44,9 +45,10 @@ export class ComputedRefImpl { this.effect = new ReactiveEffect(getter, () => { if (!this._scheduled) { this._scheduled = true - triggerRefValue(this, TriggerType.ComputedDepsUpdated) + triggerRefValue(this, TriggerTypes.ComputedDepsUpdated) } }) + this.effect._triggerFlags |= TriggerTypes.ComputedValueUpdated this.effect.computed = this this.effect.active = this._cacheable = !isSSR this[ReactiveFlags.IS_READONLY] = isReadonly @@ -59,7 +61,7 @@ export class ComputedRefImpl { if (!self._cacheable || self.effect.dirty) { const newValue = self.effect.run()! if (hasChanged(self._value, newValue)) { - triggerRefValue(self, TriggerType.ComputedValueUpdated) + triggerRefValue(self, TriggerTypes.ComputedValueUpdated) } self._value = newValue self._scheduled = false diff --git a/packages/reactivity/src/deferredComputed.ts b/packages/reactivity/src/deferredComputed.ts index 67893cd36ab..16f708c2b75 100644 --- a/packages/reactivity/src/deferredComputed.ts +++ b/packages/reactivity/src/deferredComputed.ts @@ -1,5 +1,5 @@ import { Dep } from './dep' -import { ReactiveEffect, TriggerType } from './effect' +import { ReactiveEffect } from './effect' import { ComputedGetter, ComputedRef } from './computed' import { ReactiveFlags, toRaw } from './reactive' import { trackRefValue, triggerRefValue } from './ref' @@ -38,7 +38,7 @@ class DeferredComputedRefImpl { let compareTarget: any let hasCompareTarget = false let scheduled = false - this.effect = new ReactiveEffect(getter, (_, computedTrigger?: boolean) => { + this.effect = new ReactiveEffect(getter, (computedTrigger?: boolean) => { if (this.dep) { if (computedTrigger) { compareTarget = this._value @@ -59,7 +59,7 @@ class DeferredComputedRefImpl { // deferred to be triggered in scheduler. for (const e of this.dep) { if (e.computed instanceof DeferredComputedRefImpl) { - e.scheduler(TriggerType.ForceDirty, true /* computedTrigger */) + e.scheduler(true /* computedTrigger */) } } } diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 6466cc72849..75c3f44cb94 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -1,4 +1,4 @@ -import { TrackOpTypes, TriggerOpTypes } from './operations' +import { TrackOpTypes, TriggerOpTypes, TriggerTypes } from './operations' import { extend, isArray, isIntegerKey, isMap } from '@vue/shared' import { EffectScope, recordEffectScope } from './effectScope' import { @@ -30,13 +30,7 @@ export let trackOpBit = 1 */ const maxMarkerBits = 30 -export enum TriggerType { - ForceDirty = 1, - ComputedDepsUpdated = 2, - ComputedValueUpdated = 3 -} - -export type EffectScheduler = (triggerType: TriggerType, ...args: any[]) => any +export type EffectScheduler = (...args: any[]) => any export type DebuggerEvent = { effect: ReactiveEffect @@ -87,6 +81,7 @@ export class ReactiveEffect { _dirty = true _depsMaybeDirty = false + _triggerFlags = TriggerTypes.ForceDirty | TriggerTypes.ComputedDepsUpdated constructor( public fn: () => T, @@ -222,22 +217,12 @@ export function effect( fn = (fn as ReactiveEffectRunner).effect.fn } - let scheduled = false - - const _effect = new ReactiveEffect(fn, triggerType => { - if ( - triggerType === TriggerType.ForceDirty || - triggerType === TriggerType.ComputedDepsUpdated || - !scheduled - ) { - scheduled = true - queueEffectCbs.push(() => { - if (_effect.dirty) { - _effect.run() - } - scheduled = false - }) - } + const _effect = new ReactiveEffect(fn, () => { + queueEffectCbs.push(() => { + if (_effect.dirty) { + _effect.run() + } + }) }) if (options) { extend(_effect, options) @@ -423,9 +408,9 @@ export function trigger( if (deps.length === 1) { if (deps[0]) { if (__DEV__) { - triggerEffects(TriggerType.ForceDirty, deps[0], eventInfo) + triggerEffects(TriggerTypes.ForceDirty, deps[0], eventInfo) } else { - triggerEffects(TriggerType.ForceDirty, deps[0]) + triggerEffects(TriggerTypes.ForceDirty, deps[0]) } } } else { @@ -436,15 +421,15 @@ export function trigger( } } if (__DEV__) { - triggerEffects(TriggerType.ForceDirty, createDep(effects), eventInfo) + triggerEffects(TriggerTypes.ForceDirty, createDep(effects), eventInfo) } else { - triggerEffects(TriggerType.ForceDirty, createDep(effects)) + triggerEffects(TriggerTypes.ForceDirty, createDep(effects)) } } } export function triggerEffects( - triggerType: TriggerType, + triggerType: TriggerTypes, dep: Dep, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { @@ -465,7 +450,7 @@ export function triggerEffects( const queueEffectCbs: (() => void)[] = [] function triggerEffect( - triggerType: TriggerType, + triggerType: TriggerTypes, effect: ReactiveEffect, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { @@ -474,18 +459,20 @@ function triggerEffect( effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) } if (!effect._dirty) { - if (triggerType === TriggerType.ComputedDepsUpdated) { + if (triggerType === TriggerTypes.ComputedDepsUpdated) { effect._depsMaybeDirty = true } else if ( - triggerType === TriggerType.ComputedValueUpdated && + triggerType === TriggerTypes.ComputedValueUpdated && (effect.computed || effect._depsMaybeDirty) ) { effect.dirty = true - } else if (triggerType === TriggerType.ForceDirty) { + } else if (triggerType === TriggerTypes.ForceDirty) { effect.dirty = true } } - effect.scheduler(triggerType) + if (effect._triggerFlags & triggerType) { + effect.scheduler(triggerType) + } } scheduleEffectCallbacks() } diff --git a/packages/reactivity/src/index.ts b/packages/reactivity/src/index.ts index 05c3ac6ab56..ee4da5b1935 100644 --- a/packages/reactivity/src/index.ts +++ b/packages/reactivity/src/index.ts @@ -56,7 +56,6 @@ export { resetTracking, ITERATE_KEY, ReactiveEffect, - TriggerType, type ReactiveEffectRunner, type ReactiveEffectOptions, type EffectScheduler, diff --git a/packages/reactivity/src/operations.ts b/packages/reactivity/src/operations.ts index 1b96e982571..024aa4f267b 100644 --- a/packages/reactivity/src/operations.ts +++ b/packages/reactivity/src/operations.ts @@ -13,3 +13,9 @@ export const enum TriggerOpTypes { DELETE = 'delete', CLEAR = 'clear' } + +export const enum TriggerTypes { + ForceDirty = 1 << 0, + ComputedDepsUpdated = 1 << 1, + ComputedValueUpdated = 1 << 2 +} diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index c38b777c738..8b2c2d8bcd3 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -1,12 +1,11 @@ import { - TriggerType, activeEffect, getDepFromReactive, shouldTrack, trackEffects, triggerEffects } from './effect' -import { TrackOpTypes, TriggerOpTypes } from './operations' +import { TrackOpTypes, TriggerOpTypes, TriggerTypes } from './operations' import { isArray, hasChanged, IfAny, isFunction, isObject } from '@vue/shared' import { isProxy, @@ -59,7 +58,7 @@ export function trackRefValue( export function triggerRefValue( ref: RefBase, - triggerType: TriggerType = TriggerType.ForceDirty, + triggerType: TriggerTypes = TriggerTypes.ForceDirty, newVal?: any ) { ref = toRaw(ref) @@ -167,7 +166,7 @@ class RefImpl { if (hasChanged(newVal, this._rawValue)) { this._rawValue = newVal this._value = useDirectValue ? newVal : toReactive(newVal) - triggerRefValue(this, TriggerType.ForceDirty, newVal) + triggerRefValue(this, TriggerTypes.ForceDirty, newVal) } } } @@ -198,7 +197,7 @@ class RefImpl { * @see {@link https://vuejs.org/api/reactivity-advanced.html#triggerref} */ export function triggerRef(ref: Ref) { - triggerRefValue(ref, TriggerType.ForceDirty, __DEV__ ? ref.value : void 0) + triggerRefValue(ref, TriggerTypes.ForceDirty, __DEV__ ? ref.value : void 0) } export type MaybeRef = T | Ref diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 003a8e77fa3..eeee23b391d 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -45,12 +45,7 @@ import { flushPreFlushCbs, SchedulerJob } from './scheduler' -import { - pauseTracking, - resetTracking, - ReactiveEffect, - TriggerType -} from '@vue/reactivity' +import { pauseTracking, resetTracking, ReactiveEffect } from '@vue/reactivity' import { updateProps } from './componentProps' import { updateSlots } from './componentSlots' import { pushWarningContext, popWarningContext, warn } from './warning' @@ -1549,21 +1544,10 @@ function baseCreateRenderer( } } - let scheduled = false - // create reactive effect for rendering const effect = (instance.effect = new ReactiveEffect( componentUpdateFn, - triggerType => { - if ( - triggerType === TriggerType.ForceDirty || - triggerType === TriggerType.ComputedDepsUpdated || - !scheduled - ) { - scheduled = true - queueJob(update) - } - }, + () => queueJob(update), instance.scope // track it in component's effect scope )) @@ -1571,7 +1555,6 @@ function baseCreateRenderer( if (effect.dirty) { effect.run() } - scheduled = false }) update.id = instance.uid // allowRecurse From f3625b0ffff0951bea8f83683f1bf1346edc5693 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 2 Sep 2023 07:09:58 +0800 Subject: [PATCH 096/192] chore: fix deferredComputed --- packages/reactivity/src/effect.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 75c3f44cb94..e6506b8a95c 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -471,7 +471,7 @@ function triggerEffect( } } if (effect._triggerFlags & triggerType) { - effect.scheduler(triggerType) + effect.scheduler() } } scheduleEffectCallbacks() From f41bc9b8a62826d41b308e2b097077694e3183b0 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 2 Sep 2023 07:14:40 +0800 Subject: [PATCH 097/192] feat: redo apiWatch --- .../runtime-core/__tests__/apiWatch.spec.ts | 25 +++++++++++++++++++ packages/runtime-core/src/apiWatch.ts | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index f24ce80b9df..7ead1011e25 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -1205,4 +1205,29 @@ describe('api: watch', () => { expect(countWE).toBe(3) expect(countW).toBe(2) }) + + it('watch callback on-demand trigger', () => { + const effectSpy = vi.fn() + + const sec = ref(0) + const min = computed(() => { + return Math.floor(sec.value / 60) + }) + const hour = computed(() => { + return Math.floor(min.value / 60) + }) + + watchEffect( + () => { + effectSpy() + min.value + hour.value + }, + { flush: 'sync' } + ) + + for (sec.value = 0; sec.value < 1000; sec.value++) {} + + expect(effectSpy).toHaveBeenCalledTimes(17) + }) }) diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 1b85ba12d19..4b57c4b355d 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -307,7 +307,7 @@ function doWatch( ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE) : INITIAL_WATCHER_VALUE const job: SchedulerJob = () => { - if (!effect.active) { + if (!effect.active || !effect.dirty) { return } if (cb) { From 09d6f0552161b2ddefd3e8b31acaabf38a92a652 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 2 Sep 2023 09:01:10 +0800 Subject: [PATCH 098/192] fix: continuous effects cause computed dirty race condition --- packages/reactivity/__tests__/computed.spec.ts | 18 ++++++++++++++++++ packages/reactivity/src/effect.ts | 6 ++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/reactivity/__tests__/computed.spec.ts b/packages/reactivity/__tests__/computed.spec.ts index 2a3629d66ce..6ee6f913379 100644 --- a/packages/reactivity/__tests__/computed.spec.ts +++ b/packages/reactivity/__tests__/computed.spec.ts @@ -359,4 +359,22 @@ describe('reactivity/computed', () => { d.value expect(cSpy).toHaveBeenCalledTimes(1) }) + + test('should not continuous effects cause computed dirty race condition', () => { + const fnSpy = vi.fn() + const v = ref(1) + const c = computed(() => v.value) + + effect(() => { + c.value + }) + effect(() => { + c.value + fnSpy() + }) + + expect(fnSpy).toBeCalledTimes(1) + v.value = 2 + expect(fnSpy).toBeCalledTimes(2) + }) }) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index e6506b8a95c..8f440472ea5 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -462,12 +462,10 @@ function triggerEffect( if (triggerType === TriggerTypes.ComputedDepsUpdated) { effect._depsMaybeDirty = true } else if ( - triggerType === TriggerTypes.ComputedValueUpdated && - (effect.computed || effect._depsMaybeDirty) + triggerType === TriggerTypes.ComputedValueUpdated || + triggerType === TriggerTypes.ForceDirty ) { effect.dirty = true - } else if (triggerType === TriggerTypes.ForceDirty) { - effect.dirty = true } } if (effect._triggerFlags & triggerType) { From 576be5f0bcdb31a092ca9bf3b16ea3c1b832b8cc Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 2 Sep 2023 11:33:28 +0800 Subject: [PATCH 099/192] fix: render triggers twice --- packages/reactivity/src/computed.ts | 7 ++-- packages/reactivity/src/effect.ts | 60 +++++++++++++-------------- packages/reactivity/src/operations.ts | 9 ++-- packages/reactivity/src/ref.ts | 12 +++--- 4 files changed, 44 insertions(+), 44 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index aab3098fe30..9a58701ddd1 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -3,7 +3,7 @@ import { Ref, trackRefValue, triggerRefValue } from './ref' import { hasChanged, isFunction, NOOP } from '@vue/shared' import { ReactiveFlags, toRaw } from './reactive' import { Dep } from './dep' -import { TriggerTypes } from './operations' +import { DirtyLevels } from './operations' declare const ComputedRefSymbol: unique symbol @@ -45,10 +45,9 @@ export class ComputedRefImpl { this.effect = new ReactiveEffect(getter, () => { if (!this._scheduled) { this._scheduled = true - triggerRefValue(this, TriggerTypes.ComputedDepsUpdated) + triggerRefValue(this, DirtyLevels.DepsMaybeDirty) } }) - this.effect._triggerFlags |= TriggerTypes.ComputedValueUpdated this.effect.computed = this this.effect.active = this._cacheable = !isSSR this[ReactiveFlags.IS_READONLY] = isReadonly @@ -61,7 +60,7 @@ export class ComputedRefImpl { if (!self._cacheable || self.effect.dirty) { const newValue = self.effect.run()! if (hasChanged(self._value, newValue)) { - triggerRefValue(self, TriggerTypes.ComputedValueUpdated) + triggerRefValue(self, DirtyLevels.ComputedValueDirty) } self._value = newValue self._scheduled = false diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 8f440472ea5..e6e23a0ccfa 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -1,4 +1,4 @@ -import { TrackOpTypes, TriggerOpTypes, TriggerTypes } from './operations' +import { DirtyLevels, TrackOpTypes, TriggerOpTypes } from './operations' import { extend, isArray, isIntegerKey, isMap } from '@vue/shared' import { EffectScope, recordEffectScope } from './effectScope' import { @@ -79,9 +79,7 @@ export class ReactiveEffect { // dev only onTrigger?: (event: DebuggerEvent) => void - _dirty = true - _depsMaybeDirty = false - _triggerFlags = TriggerTypes.ForceDirty | TriggerTypes.ComputedDepsUpdated + _dirtyLevel = DirtyLevels.Dirty constructor( public fn: () => T, @@ -92,30 +90,36 @@ export class ReactiveEffect { } public get dirty() { - if (!this._dirty && this._depsMaybeDirty) { + if (this._dirtyLevel === DirtyLevels.DepsMaybeDirty) { + this._dirtyLevel = DirtyLevels.NotDirty pauseTracking() for (const dep of this.deps) { if (dep.computed?._scheduled) { triggerComputedGetter(dep.computed) // wrap with function call to avoid tree shaking - if (this._dirty) { + if (this._dirtyLevel >= DirtyLevels.ComputedValueDirty) { break } } } resetTracking() } - this._depsMaybeDirty = false - return this._dirty + return this._dirtyLevel >= DirtyLevels.ComputedValueDirty } public set dirty(v) { - this._dirty = v - this._depsMaybeDirty = false + this._dirtyLevel = v ? DirtyLevels.Dirty : DirtyLevels.NotDirty } run() { - this._dirty = false - this._depsMaybeDirty = false + this._dirtyLevel = DirtyLevels.NotDirty + const result = this._run() + if ((this._dirtyLevel as DirtyLevels) === DirtyLevels.ComputedValueDirty) { + this._dirtyLevel = DirtyLevels.NotDirty + } + return result + } + + _run() { if (!this.active) { return this.fn() } @@ -408,9 +412,9 @@ export function trigger( if (deps.length === 1) { if (deps[0]) { if (__DEV__) { - triggerEffects(TriggerTypes.ForceDirty, deps[0], eventInfo) + triggerEffects(deps[0], DirtyLevels.Dirty, eventInfo) } else { - triggerEffects(TriggerTypes.ForceDirty, deps[0]) + triggerEffects(deps[0], DirtyLevels.Dirty) } } } else { @@ -421,28 +425,28 @@ export function trigger( } } if (__DEV__) { - triggerEffects(TriggerTypes.ForceDirty, createDep(effects), eventInfo) + triggerEffects(createDep(effects), DirtyLevels.Dirty, eventInfo) } else { - triggerEffects(TriggerTypes.ForceDirty, createDep(effects)) + triggerEffects(createDep(effects), DirtyLevels.Dirty) } } } export function triggerEffects( - triggerType: TriggerTypes, dep: Dep, + dirtyLevel: DirtyLevels, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { // spread into array for stabilization const effects = [...dep] for (const effect of effects) { if (effect.computed) { - triggerEffect(triggerType, effect, debuggerEventExtraInfo) + triggerEffect(effect, dirtyLevel, debuggerEventExtraInfo) } } for (const effect of effects) { if (!effect.computed) { - triggerEffect(triggerType, effect, debuggerEventExtraInfo) + triggerEffect(effect, dirtyLevel, debuggerEventExtraInfo) } } } @@ -450,25 +454,21 @@ export function triggerEffects( const queueEffectCbs: (() => void)[] = [] function triggerEffect( - triggerType: TriggerTypes, effect: ReactiveEffect, + dirtyLevel: DirtyLevels, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { if (effect !== activeEffect || effect.allowRecurse) { if (__DEV__ && effect.onTrigger) { effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) } - if (!effect._dirty) { - if (triggerType === TriggerTypes.ComputedDepsUpdated) { - effect._depsMaybeDirty = true - } else if ( - triggerType === TriggerTypes.ComputedValueUpdated || - triggerType === TriggerTypes.ForceDirty - ) { - effect.dirty = true - } + if (effect._dirtyLevel < dirtyLevel) { + effect._dirtyLevel = dirtyLevel } - if (effect._triggerFlags & triggerType) { + if ( + dirtyLevel === DirtyLevels.DepsMaybeDirty || + dirtyLevel === DirtyLevels.Dirty + ) { effect.scheduler() } } diff --git a/packages/reactivity/src/operations.ts b/packages/reactivity/src/operations.ts index 024aa4f267b..2e3d89ddfd2 100644 --- a/packages/reactivity/src/operations.ts +++ b/packages/reactivity/src/operations.ts @@ -14,8 +14,9 @@ export const enum TriggerOpTypes { CLEAR = 'clear' } -export const enum TriggerTypes { - ForceDirty = 1 << 0, - ComputedDepsUpdated = 1 << 1, - ComputedValueUpdated = 1 << 2 +export const enum DirtyLevels { + NotDirty = 0, + DepsMaybeDirty = 1, + ComputedValueDirty = 2, + Dirty = 3 } diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 8b2c2d8bcd3..1d789353064 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -5,7 +5,7 @@ import { trackEffects, triggerEffects } from './effect' -import { TrackOpTypes, TriggerOpTypes, TriggerTypes } from './operations' +import { DirtyLevels, TrackOpTypes, TriggerOpTypes } from './operations' import { isArray, hasChanged, IfAny, isFunction, isObject } from '@vue/shared' import { isProxy, @@ -58,21 +58,21 @@ export function trackRefValue( export function triggerRefValue( ref: RefBase, - triggerType: TriggerTypes = TriggerTypes.ForceDirty, + dirtyLevel: DirtyLevels = DirtyLevels.Dirty, newVal?: any ) { ref = toRaw(ref) const dep = ref.dep if (dep) { if (__DEV__) { - triggerEffects(triggerType, dep, { + triggerEffects(dep, dirtyLevel, { target: ref, type: TriggerOpTypes.SET, key: 'value', newValue: newVal }) } else { - triggerEffects(triggerType, dep) + triggerEffects(dep, dirtyLevel) } } } @@ -166,7 +166,7 @@ class RefImpl { if (hasChanged(newVal, this._rawValue)) { this._rawValue = newVal this._value = useDirectValue ? newVal : toReactive(newVal) - triggerRefValue(this, TriggerTypes.ForceDirty, newVal) + triggerRefValue(this, DirtyLevels.Dirty, newVal) } } } @@ -197,7 +197,7 @@ class RefImpl { * @see {@link https://vuejs.org/api/reactivity-advanced.html#triggerref} */ export function triggerRef(ref: Ref) { - triggerRefValue(ref, TriggerTypes.ForceDirty, __DEV__ ? ref.value : void 0) + triggerRefValue(ref, DirtyLevels.Dirty, __DEV__ ? ref.value : void 0) } export type MaybeRef = T | Ref From 7058975890e58145b9f880777e1010a15963e231 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 2 Sep 2023 11:49:10 +0800 Subject: [PATCH 100/192] refactor: remove queueEffectCbs --- packages/reactivity/src/effect.ts | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index e6e23a0ccfa..cca91a89630 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -114,7 +114,7 @@ export class ReactiveEffect { this._dirtyLevel = DirtyLevels.NotDirty const result = this._run() if ((this._dirtyLevel as DirtyLevels) === DirtyLevels.ComputedValueDirty) { - this._dirtyLevel = DirtyLevels.NotDirty + this._dirtyLevel-- } return result } @@ -222,11 +222,9 @@ export function effect( } const _effect = new ReactiveEffect(fn, () => { - queueEffectCbs.push(() => { - if (_effect.dirty) { - _effect.run() - } - }) + if (_effect.dirty) { + _effect.run() + } }) if (options) { extend(_effect, options) @@ -451,8 +449,6 @@ export function triggerEffects( } } -const queueEffectCbs: (() => void)[] = [] - function triggerEffect( effect: ReactiveEffect, dirtyLevel: DirtyLevels, @@ -472,15 +468,6 @@ function triggerEffect( effect.scheduler() } } - scheduleEffectCallbacks() -} - -function scheduleEffectCallbacks() { - if (effectTrackDepth === 0) { - while (queueEffectCbs.length) { - queueEffectCbs.shift()!() - } - } } export function getDepFromReactive(object: any, key: string | number | symbol) { From b3f502ed5fd017e2ad062967f8fe7a0c149c8fcd Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 3 Sep 2023 11:11:20 +0800 Subject: [PATCH 101/192] chore: DepsMaybeDirty -> ComputedValueMaybeDirty --- packages/reactivity/src/computed.ts | 2 +- packages/reactivity/src/effect.ts | 4 ++-- packages/reactivity/src/operations.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 9a58701ddd1..d61c341b7d5 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -45,7 +45,7 @@ export class ComputedRefImpl { this.effect = new ReactiveEffect(getter, () => { if (!this._scheduled) { this._scheduled = true - triggerRefValue(this, DirtyLevels.DepsMaybeDirty) + triggerRefValue(this, DirtyLevels.ComputedValueMaybeDirty) } }) this.effect.computed = this diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index cca91a89630..bcd1619f280 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -90,7 +90,7 @@ export class ReactiveEffect { } public get dirty() { - if (this._dirtyLevel === DirtyLevels.DepsMaybeDirty) { + if (this._dirtyLevel === DirtyLevels.ComputedValueMaybeDirty) { this._dirtyLevel = DirtyLevels.NotDirty pauseTracking() for (const dep of this.deps) { @@ -462,7 +462,7 @@ function triggerEffect( effect._dirtyLevel = dirtyLevel } if ( - dirtyLevel === DirtyLevels.DepsMaybeDirty || + dirtyLevel === DirtyLevels.ComputedValueMaybeDirty || dirtyLevel === DirtyLevels.Dirty ) { effect.scheduler() diff --git a/packages/reactivity/src/operations.ts b/packages/reactivity/src/operations.ts index 2e3d89ddfd2..26b231e3dc6 100644 --- a/packages/reactivity/src/operations.ts +++ b/packages/reactivity/src/operations.ts @@ -16,7 +16,7 @@ export const enum TriggerOpTypes { export const enum DirtyLevels { NotDirty = 0, - DepsMaybeDirty = 1, + ComputedValueMaybeDirty = 1, ComputedValueDirty = 2, Dirty = 3 } From a1cb22a59b19266781e9fd7c910582212a8575b1 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 3 Sep 2023 11:57:27 +0800 Subject: [PATCH 102/192] fix: computed scheduler should always trigger --- packages/reactivity/src/effect.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index bcd1619f280..b9c28962761 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -463,7 +463,9 @@ function triggerEffect( } if ( dirtyLevel === DirtyLevels.ComputedValueMaybeDirty || - dirtyLevel === DirtyLevels.Dirty + dirtyLevel === DirtyLevels.Dirty || + // computed scheduler should always trigger + effect.computed ) { effect.scheduler() } From bf85f9aa93b7d93129f3b7d2383900ccc908698d Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 3 Sep 2023 12:06:28 +0800 Subject: [PATCH 103/192] refactor: remove computed hack --- packages/reactivity/src/effect.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index b9c28962761..39f90636dde 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -80,6 +80,7 @@ export class ReactiveEffect { onTrigger?: (event: DebuggerEvent) => void _dirtyLevel = DirtyLevels.Dirty + _queryingDirty = false constructor( public fn: () => T, @@ -92,6 +93,7 @@ export class ReactiveEffect { public get dirty() { if (this._dirtyLevel === DirtyLevels.ComputedValueMaybeDirty) { this._dirtyLevel = DirtyLevels.NotDirty + this._queryingDirty = true pauseTracking() for (const dep of this.deps) { if (dep.computed?._scheduled) { @@ -102,6 +104,7 @@ export class ReactiveEffect { } } resetTracking() + this._queryingDirty = false } return this._dirtyLevel >= DirtyLevels.ComputedValueDirty } @@ -464,8 +467,7 @@ function triggerEffect( if ( dirtyLevel === DirtyLevels.ComputedValueMaybeDirty || dirtyLevel === DirtyLevels.Dirty || - // computed scheduler should always trigger - effect.computed + (dirtyLevel === DirtyLevels.ComputedValueDirty && !effect._queryingDirty) ) { effect.scheduler() } From 710c7c5fff4fc0c00269254175de40d9e7bec372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=B6=E8=BF=9C=E6=96=B9?= Date: Wed, 30 Aug 2023 15:25:51 +0800 Subject: [PATCH 104/192] feat(runtime-core): add `once` option to watch (#9034) --- .../runtime-core/__tests__/apiWatch.spec.ts | 38 +++++++++++++++++++ packages/runtime-core/src/apiWatch.ts | 31 +++++++++++---- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index f24ce80b9df..bddfc5ff541 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -1205,4 +1205,42 @@ describe('api: watch', () => { expect(countWE).toBe(3) expect(countW).toBe(2) }) + + const options = [ + { name: 'only trigger once watch' }, + { + deep: true, + name: 'only trigger once watch with deep' + }, + { + flush: 'sync', + name: 'only trigger once watch with flush: sync' + }, + { + flush: 'pre', + name: 'only trigger once watch with flush: pre' + }, + { + immediate: true, + name: 'only trigger once watch with immediate' + } + ] as const + test.each(options)('$name', async option => { + const count = ref(0) + const cb = vi.fn() + + watch(count, cb, { once: true, ...option }) + + count.value++ + await nextTick() + + expect(count.value).toBe(1) + expect(cb).toHaveBeenCalledTimes(1) + + count.value++ + await nextTick() + + expect(count.value).toBe(2) + expect(cb).toHaveBeenCalledTimes(1) + }) }) diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 1b85ba12d19..c307c4198a3 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -75,6 +75,7 @@ export interface WatchOptionsBase extends DebuggerOptions { export interface WatchOptions extends WatchOptionsBase { immediate?: Immediate deep?: boolean + once?: boolean } export type WatchStopHandle = () => void @@ -172,8 +173,16 @@ export function watch = false>( function doWatch( source: WatchSource | WatchSource[] | WatchEffect | object, cb: WatchCallback | null, - { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ + { immediate, deep, flush, once, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ ): WatchStopHandle { + if (cb && once) { + const _cb = cb + cb = (...args) => { + _cb(...args) + unwatch() + } + } + if (__DEV__ && !cb) { if (immediate !== undefined) { warn( @@ -187,6 +196,12 @@ function doWatch( `watch(source, callback, options?) signature.` ) } + if (once !== undefined) { + warn( + `watch() "once" option is only respected when using the ` + + `watch(source, callback, options?) signature.` + ) + } } const warnInvalidSource = (s: unknown) => { @@ -363,6 +378,13 @@ function doWatch( const effect = new ReactiveEffect(getter, scheduler) + const unwatch = () => { + effect.stop() + if (instance && instance.scope) { + remove(instance.scope.effects!, effect) + } + } + if (__DEV__) { effect.onTrack = onTrack effect.onTrigger = onTrigger @@ -384,13 +406,6 @@ function doWatch( effect.run() } - const unwatch = () => { - effect.stop() - if (instance && instance.scope) { - remove(instance.scope.effects!, effect) - } - } - if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch) return unwatch } From f5088d29bd3c49ad72109ca974553fab1c4ac55d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Mon, 4 Sep 2023 03:59:11 -0500 Subject: [PATCH 105/192] feat(compiler-sfc): expose resolve type-based props and emits (#8874) --- packages/compiler-sfc/src/index.ts | 3 +++ .../compiler-sfc/src/script/defineEmits.ts | 10 ++++--- .../compiler-sfc/src/script/defineProps.ts | 22 ++++++++++------ .../compiler-sfc/src/script/resolveType.ts | 26 +++++++++++++++++-- 4 files changed, 48 insertions(+), 13 deletions(-) diff --git a/packages/compiler-sfc/src/index.ts b/packages/compiler-sfc/src/index.ts index 76b4900d46d..c6ee604146e 100644 --- a/packages/compiler-sfc/src/index.ts +++ b/packages/compiler-sfc/src/index.ts @@ -33,6 +33,8 @@ export { // Internals for type resolution export { invalidateTypeCache, registerTS } from './script/resolveType' +export { extractRuntimeProps } from './script/defineProps' +export { extractRuntimeEmits } from './script/defineEmits' // Types export type { @@ -58,6 +60,7 @@ export type { SFCScriptCompileOptions } from './compileScript' export type { ScriptCompileContext } from './script/context' export type { TypeResolveContext, + SimpleTypeResolveOptions, SimpleTypeResolveContext } from './script/resolveType' export type { diff --git a/packages/compiler-sfc/src/script/defineEmits.ts b/packages/compiler-sfc/src/script/defineEmits.ts index 02014d1b276..9f0184af836 100644 --- a/packages/compiler-sfc/src/script/defineEmits.ts +++ b/packages/compiler-sfc/src/script/defineEmits.ts @@ -1,7 +1,11 @@ import { Identifier, LVal, Node, RestElement } from '@babel/types' import { isCallOf } from './utils' import { ScriptCompileContext } from './context' -import { resolveTypeElements, resolveUnionType } from './resolveType' +import { + TypeResolveContext, + resolveTypeElements, + resolveUnionType +} from './resolveType' export const DEFINE_EMITS = 'defineEmits' @@ -57,7 +61,7 @@ export function genRuntimeEmits(ctx: ScriptCompileContext): string | undefined { return emitsDecl } -function extractRuntimeEmits(ctx: ScriptCompileContext): Set { +export function extractRuntimeEmits(ctx: TypeResolveContext): Set { const emits = new Set() const node = ctx.emitsTypeDecl! @@ -90,7 +94,7 @@ function extractRuntimeEmits(ctx: ScriptCompileContext): Set { } function extractEventNames( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, eventName: Identifier | RestElement, emits: Set ) { diff --git a/packages/compiler-sfc/src/script/defineProps.ts b/packages/compiler-sfc/src/script/defineProps.ts index 5004e314da1..449ed250d1d 100644 --- a/packages/compiler-sfc/src/script/defineProps.ts +++ b/packages/compiler-sfc/src/script/defineProps.ts @@ -8,7 +8,11 @@ import { } from '@babel/types' import { BindingTypes, isFunctionType } from '@vue/compiler-dom' import { ScriptCompileContext } from './context' -import { inferRuntimeType, resolveTypeElements } from './resolveType' +import { + TypeResolveContext, + inferRuntimeType, + resolveTypeElements +} from './resolveType' import { resolveObjectKey, UNKNOWN_TYPE, @@ -150,7 +154,7 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined { } } } else if (ctx.propsTypeDecl) { - propsDecls = genRuntimePropsFromTypes(ctx) + propsDecls = extractRuntimeProps(ctx) } const modelsDecls = genModelProps(ctx) @@ -162,7 +166,9 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined { } } -function genRuntimePropsFromTypes(ctx: ScriptCompileContext) { +export function extractRuntimeProps( + ctx: TypeResolveContext +): string | undefined { // this is only called if propsTypeDecl exists const props = resolveRuntimePropsFromType(ctx, ctx.propsTypeDecl!) if (!props.length) { @@ -175,7 +181,7 @@ function genRuntimePropsFromTypes(ctx: ScriptCompileContext) { for (const prop of props) { propStrings.push(genRuntimePropFromType(ctx, prop, hasStaticDefaults)) // register bindings - if (!(prop.key in ctx.bindingMetadata)) { + if ('bindingMetadata' in ctx && !(prop.key in ctx.bindingMetadata)) { ctx.bindingMetadata[prop.key] = BindingTypes.PROPS } } @@ -193,7 +199,7 @@ function genRuntimePropsFromTypes(ctx: ScriptCompileContext) { } function resolveRuntimePropsFromType( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, node: Node ): PropTypeData[] { const props: PropTypeData[] = [] @@ -222,7 +228,7 @@ function resolveRuntimePropsFromType( } function genRuntimePropFromType( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, { key, required, type, skipCheck }: PropTypeData, hasStaticDefaults: boolean ): string { @@ -284,7 +290,7 @@ function genRuntimePropFromType( * static properties, we can directly generate more optimized default * declarations. Otherwise we will have to fallback to runtime merging. */ -function hasStaticWithDefaults(ctx: ScriptCompileContext) { +function hasStaticWithDefaults(ctx: TypeResolveContext) { return !!( ctx.propsRuntimeDefaults && ctx.propsRuntimeDefaults.type === 'ObjectExpression' && @@ -297,7 +303,7 @@ function hasStaticWithDefaults(ctx: ScriptCompileContext) { } function genDestructuredDefaultValue( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, key: string, inferredType?: string[] ): diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index 146c454729c..315c3d98405 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -42,6 +42,13 @@ import type TS from 'typescript' import { extname, dirname } from 'path' import { minimatch as isMatch } from 'minimatch' +export type SimpleTypeResolveOptions = Partial< + Pick< + SFCScriptCompileOptions, + 'globalTypeFiles' | 'fs' | 'babelParserPlugins' | 'isProd' + > +> + /** * TypeResolveContext is compatible with ScriptCompileContext * but also allows a simpler version of it with minimal required properties @@ -59,13 +66,28 @@ import { minimatch as isMatch } from 'minimatch' */ export type SimpleTypeResolveContext = Pick< ScriptCompileContext, - // required - 'source' | 'filename' | 'error' | 'options' + // file + | 'source' + | 'filename' + + // utils + | 'error' + | 'helper' + | 'getString' + + // props + | 'propsTypeDecl' + | 'propsRuntimeDefaults' + | 'propsDestructuredBindings' + + // emits + | 'emitsTypeDecl' > & Partial< Pick > & { ast: Statement[] + options: SimpleTypeResolveOptions } export type TypeResolveContext = ScriptCompileContext | SimpleTypeResolveContext From ef8070c19f1a8c4d075e163bf1df9df1c1576861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8A=B1=E6=9E=9C=E5=B1=B1=E5=A4=A7=E5=9C=A3?= <316783812@qq.com> Date: Tue, 5 Sep 2023 15:55:39 +0800 Subject: [PATCH 106/192] feat(compiler-core): export error message (#8729) --- packages/compiler-core/src/index.ts | 1 + packages/compiler-dom/src/index.ts | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/compiler-core/src/index.ts b/packages/compiler-core/src/index.ts index 4898a181dfc..588bb92cc5f 100644 --- a/packages/compiler-core/src/index.ts +++ b/packages/compiler-core/src/index.ts @@ -24,6 +24,7 @@ export { export { generate, type CodegenContext, type CodegenResult } from './codegen' export { ErrorCodes, + errorMessages, createCompilerError, type CoreCompilerError, type CompilerError diff --git a/packages/compiler-dom/src/index.ts b/packages/compiler-dom/src/index.ts index 2c6f71cefbb..a2f4aff2e4c 100644 --- a/packages/compiler-dom/src/index.ts +++ b/packages/compiler-dom/src/index.ts @@ -68,5 +68,9 @@ export function parse(template: string, options: ParserOptions = {}): RootNode { export * from './runtimeHelpers' export { transformStyle } from './transforms/transformStyle' -export { createDOMCompilerError, DOMErrorCodes } from './errors' +export { + createDOMCompilerError, + DOMErrorCodes, + DOMErrorMessages +} from './errors' export * from '@vue/compiler-core' From ab252291a19102b8555f65c187788f5aa29764dd Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 7 Sep 2023 16:44:16 +0800 Subject: [PATCH 107/192] Update deferredComputed.ts --- packages/reactivity/src/deferredComputed.ts | 51 +++++++++++---------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/packages/reactivity/src/deferredComputed.ts b/packages/reactivity/src/deferredComputed.ts index 16f708c2b75..8c4b163d0ba 100644 --- a/packages/reactivity/src/deferredComputed.ts +++ b/packages/reactivity/src/deferredComputed.ts @@ -38,33 +38,38 @@ class DeferredComputedRefImpl { let compareTarget: any let hasCompareTarget = false let scheduled = false - this.effect = new ReactiveEffect(getter, (computedTrigger?: boolean) => { - if (this.dep) { - if (computedTrigger) { - compareTarget = this._value - hasCompareTarget = true - } else if (!scheduled) { - const valueToCompare = hasCompareTarget ? compareTarget : this._value - scheduled = true - hasCompareTarget = false - scheduler(() => { - if (this.effect.active && this._get() !== valueToCompare) { - triggerRefValue(this) + this.effect = new ReactiveEffect( + getter, + (onScheduled, computedTrigger?: boolean) => { + if (this.dep) { + if (computedTrigger) { + compareTarget = this._value + hasCompareTarget = true + } else if (!scheduled) { + const valueToCompare = hasCompareTarget + ? compareTarget + : this._value + scheduled = true + hasCompareTarget = false + scheduler(() => { + if (this.effect.active && this._get() !== valueToCompare) { + triggerRefValue(this) + } + scheduled = false + }) + } + // chained upstream computeds are notified synchronously to ensure + // value invalidation in case of sync access; normal effects are + // deferred to be triggered in scheduler. + for (const e of this.dep) { + if (e.computed instanceof DeferredComputedRefImpl) { + e.scheduler(onScheduled, true /* computedTrigger */) } - scheduled = false - }) - } - // chained upstream computeds are notified synchronously to ensure - // value invalidation in case of sync access; normal effects are - // deferred to be triggered in scheduler. - for (const e of this.dep) { - if (e.computed instanceof DeferredComputedRefImpl) { - e.scheduler(true /* computedTrigger */) } } + this._dirty = true } - this._dirty = true - }) + ) this.effect.computed = this as any } From 3f5c373da4c231088543fe294292255417cc270a Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 7 Sep 2023 17:57:32 +0800 Subject: [PATCH 108/192] make new logic working for `watch()` --- packages/reactivity/src/computed.ts | 5 +++-- packages/reactivity/src/effect.ts | 6 ++++-- packages/runtime-core/src/apiWatch.ts | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index d61c341b7d5..7509d397d83 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -59,10 +59,11 @@ export class ComputedRefImpl { trackRefValue(self, self) if (!self._cacheable || self.effect.dirty) { const newValue = self.effect.run()! - if (hasChanged(self._value, newValue)) { + const changed = hasChanged(self._value, newValue) + self._value = newValue + if (changed) { triggerRefValue(self, DirtyLevels.ComputedValueDirty) } - self._value = newValue self._scheduled = false } return self._value diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 40bbc14b7cf..672cca55aea 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -457,6 +457,7 @@ export function triggerEffects( dirtyLevel: DirtyLevels, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { + pauseScheduling() // spread into array for stabilization const effects = [...dep] for (const effect of effects) { @@ -469,6 +470,8 @@ export function triggerEffects( triggerEffect(effect, dirtyLevel, debuggerEventExtraInfo) } } + resetScheduling() + scheduleEffectCallbacks() } const queueEffectCbs: (() => void)[] = [] @@ -494,11 +497,10 @@ function triggerEffect( effect.scheduler(pushEffectCb) } } - scheduleEffectCallbacks() } function scheduleEffectCallbacks() { - if (effectTrackDepth === 0 && shouldSchedule) { + if (shouldSchedule) { while (queueEffectCbs.length) { queueEffectCbs.shift()!() } diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 4b57c4b355d..e508b26b44f 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -351,7 +351,7 @@ function doWatch( let scheduler: EffectScheduler if (flush === 'sync') { - scheduler = job as any // the scheduler function gets called directly + scheduler = onScheduled => onScheduled(job as any) // the scheduler function gets called directly } else if (flush === 'post') { scheduler = () => queuePostRenderEffect(job, instance && instance.suspense) } else { From 1591ce183f5ca92e401aa64a0a66524e4e918b21 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 7 Sep 2023 17:58:26 +0800 Subject: [PATCH 109/192] update test result --- packages/reactivity/__tests__/computed.spec.ts | 2 +- packages/reactivity/__tests__/effect.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/reactivity/__tests__/computed.spec.ts b/packages/reactivity/__tests__/computed.spec.ts index 6ee6f913379..abceca3eb84 100644 --- a/packages/reactivity/__tests__/computed.spec.ts +++ b/packages/reactivity/__tests__/computed.spec.ts @@ -184,7 +184,7 @@ describe('reactivity/computed', () => { // mutate n n.value++ // on the 2nd run, plusOne.value should have already updated. - expect(plusOneValues).toMatchObject([1, 2, 2]) + expect(plusOneValues).toMatchObject([1, 2]) }) it('should warn if trying to set a readonly computed', () => { diff --git a/packages/reactivity/__tests__/effect.spec.ts b/packages/reactivity/__tests__/effect.spec.ts index 69d24a76520..f056b114846 100644 --- a/packages/reactivity/__tests__/effect.spec.ts +++ b/packages/reactivity/__tests__/effect.spec.ts @@ -559,7 +559,7 @@ describe('reactivity/effect', () => { expect(fx1Spy).toHaveBeenCalledTimes(1) // Invoked twice due to change of fx1. - expect(fx2Spy).toHaveBeenCalledTimes(2) + expect(fx2Spy).toHaveBeenCalledTimes(1) fx1Spy.mockClear() fx2Spy.mockClear() From eec0a786da0cbb4191e3896a7b1b22fdf274d38a Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 7 Sep 2023 18:11:55 +0800 Subject: [PATCH 110/192] sync #9056 --- packages/reactivity/src/computed.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index d61c341b7d5..7509d397d83 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -59,10 +59,11 @@ export class ComputedRefImpl { trackRefValue(self, self) if (!self._cacheable || self.effect.dirty) { const newValue = self.effect.run()! - if (hasChanged(self._value, newValue)) { + const changed = hasChanged(self._value, newValue) + self._value = newValue + if (changed) { triggerRefValue(self, DirtyLevels.ComputedValueDirty) } - self._value = newValue self._scheduled = false } return self._value From 01e21d2e3d133c9fc5c41caba786af671a3e5ae2 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 7 Sep 2023 18:50:54 +0800 Subject: [PATCH 111/192] update test comment --- packages/reactivity/__tests__/effect.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reactivity/__tests__/effect.spec.ts b/packages/reactivity/__tests__/effect.spec.ts index f056b114846..e673e570313 100644 --- a/packages/reactivity/__tests__/effect.spec.ts +++ b/packages/reactivity/__tests__/effect.spec.ts @@ -558,7 +558,7 @@ describe('reactivity/effect', () => { expect(output.fx2).toBe(1 + 3 + 3) expect(fx1Spy).toHaveBeenCalledTimes(1) - // Invoked twice due to change of fx1. + // Invoked once even change of fx1. expect(fx2Spy).toHaveBeenCalledTimes(1) fx1Spy.mockClear() From 0bdacad806314189d55dcfb13b4aaeee2d280125 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 7 Sep 2023 18:59:41 +0800 Subject: [PATCH 112/192] chore: remove scheduleEffectCallbacks --- packages/reactivity/src/effect.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 672cca55aea..3dcc92e66f1 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -293,7 +293,11 @@ export function pauseScheduling() { export function resetScheduling() { const last = scheduleStack.pop() shouldSchedule = last === undefined ? true : last - scheduleEffectCallbacks() + if (shouldSchedule) { + while (queueEffectCbs.length) { + queueEffectCbs.shift()!() + } + } } /** @@ -471,7 +475,6 @@ export function triggerEffects( } } resetScheduling() - scheduleEffectCallbacks() } const queueEffectCbs: (() => void)[] = [] @@ -499,14 +502,6 @@ function triggerEffect( } } -function scheduleEffectCallbacks() { - if (shouldSchedule) { - while (queueEffectCbs.length) { - queueEffectCbs.shift()!() - } - } -} - export function getDepFromReactive(object: any, key: string | number | symbol) { return targetMap.get(object)?.get(key) } From 3b0c4a75181bbe3b6ba7bbec984021d84c97d5d6 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Fri, 8 Sep 2023 14:25:42 +0800 Subject: [PATCH 113/192] combine if & while --- packages/reactivity/src/effect.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 3dcc92e66f1..dbdfb506e37 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -293,10 +293,8 @@ export function pauseScheduling() { export function resetScheduling() { const last = scheduleStack.pop() shouldSchedule = last === undefined ? true : last - if (shouldSchedule) { - while (queueEffectCbs.length) { - queueEffectCbs.shift()!() - } + while (shouldSchedule && queueEffectCbs.length) { + queueEffectCbs.shift()!() } } From f1eb3b644d60452ca9064edb2e194dcf7a6af81e Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 9 Sep 2023 11:00:30 +0800 Subject: [PATCH 114/192] deprecated deferredComputed --- .../reactivity/__tests__/computed.spec.ts | 49 +--------- .../__tests__/deferredComputed.spec.ts | 48 ++------- packages/reactivity/__tests__/effect.spec.ts | 2 +- .../__tests__/reactiveArray.spec.ts | 2 +- packages/reactivity/src/computed.ts | 2 +- packages/reactivity/src/deferredComputed.ts | 97 +------------------ packages/reactivity/src/effect.ts | 3 +- 7 files changed, 22 insertions(+), 181 deletions(-) diff --git a/packages/reactivity/__tests__/computed.spec.ts b/packages/reactivity/__tests__/computed.spec.ts index abceca3eb84..d68e4397da2 100644 --- a/packages/reactivity/__tests__/computed.spec.ts +++ b/packages/reactivity/__tests__/computed.spec.ts @@ -289,51 +289,8 @@ describe('reactivity/computed', () => { }) }) - it('chained computed value on-demand trigger', () => { - const minSpy = vi.fn() - const hourSpy = vi.fn() - - const sec = ref(0) - const min = computed(() => { - minSpy() - return Math.floor(sec.value / 60) - }) - const hour = computed(() => { - hourSpy() - return Math.floor(min.value / 60) - }) - - for (sec.value = 0; sec.value < 1000; sec.value++) { - hour.value - } - - expect(minSpy).toHaveBeenCalledTimes(1000) - expect(hourSpy).toHaveBeenCalledTimes(17) - }) - - it('effect callback on-demand trigger', () => { - const effectSpy = vi.fn() - - const sec = ref(0) - const min = computed(() => { - return Math.floor(sec.value / 60) - }) - const hour = computed(() => { - return Math.floor(min.value / 60) - }) - - effect(() => { - effectSpy() - min.value - hour.value - }) - - for (sec.value = 0; sec.value < 1000; sec.value++) {} - - expect(effectSpy).toHaveBeenCalledTimes(17) - }) - - it('chained computed value urgent assessment edge case', () => { + // https://github.com/vuejs/core/pull/5912#issuecomment-1497596875 + it('should query deps dirty sequentially', () => { const cSpy = vi.fn() const a = ref({ @@ -360,7 +317,7 @@ describe('reactivity/computed', () => { expect(cSpy).toHaveBeenCalledTimes(1) }) - test('should not continuous effects cause computed dirty race condition', () => { + it('should trigger the second effect', () => { const fnSpy = vi.fn() const v = ref(1) const c = computed(() => v.value) diff --git a/packages/reactivity/__tests__/deferredComputed.spec.ts b/packages/reactivity/__tests__/deferredComputed.spec.ts index 100f14ae358..688cdb58aba 100644 --- a/packages/reactivity/__tests__/deferredComputed.spec.ts +++ b/packages/reactivity/__tests__/deferredComputed.spec.ts @@ -1,46 +1,20 @@ -import { computed, deferredComputed, effect, ref } from '../src' +import { computed, effect, ref } from '../src' describe('deferred computed', () => { - const tick = Promise.resolve() - - test('should only trigger once on multiple mutations', async () => { - const src = ref(0) - const c = deferredComputed(() => src.value) - const spy = vi.fn() - effect(() => { - spy(c.value) - }) - expect(spy).toHaveBeenCalledTimes(1) - src.value = 1 - src.value = 2 - src.value = 3 - // not called yet - expect(spy).toHaveBeenCalledTimes(1) - await tick - // should only trigger once - expect(spy).toHaveBeenCalledTimes(2) - expect(spy).toHaveBeenCalledWith(c.value) - }) - test('should not trigger if value did not change', async () => { const src = ref(0) - const c = deferredComputed(() => src.value % 2) + const c = computed(() => src.value % 2) const spy = vi.fn() effect(() => { spy(c.value) }) expect(spy).toHaveBeenCalledTimes(1) - src.value = 1 src.value = 2 - await tick // should not trigger expect(spy).toHaveBeenCalledTimes(1) src.value = 3 - src.value = 4 - src.value = 5 - await tick // should trigger because latest value changes expect(spy).toHaveBeenCalledTimes(2) }) @@ -51,7 +25,7 @@ describe('deferred computed', () => { const c2Spy = vi.fn() const src = ref(0) - const c1 = deferredComputed(() => { + const c1 = computed(() => { c1Spy() return src.value % 2 }) @@ -69,7 +43,6 @@ describe('deferred computed', () => { expect(effectSpy).toHaveBeenCalledTimes(1) src.value = 1 - await tick expect(c1Spy).toHaveBeenCalledTimes(2) expect(c2Spy).toHaveBeenCalledTimes(2) expect(effectSpy).toHaveBeenCalledTimes(2) @@ -81,7 +54,7 @@ describe('deferred computed', () => { const c2Spy = vi.fn() const src = ref(0) - const c1 = deferredComputed(() => { + const c1 = computed(() => { c1Spy() return src.value % 2 }) @@ -98,7 +71,6 @@ describe('deferred computed', () => { src.value = 2 src.value = 4 src.value = 6 - await tick // c1 should re-compute once. expect(c1Spy).toHaveBeenCalledTimes(2) // c2 should not have to re-compute because c1 did not change. @@ -113,11 +85,11 @@ describe('deferred computed', () => { const c2Spy = vi.fn() const src = ref(0) - const c1 = deferredComputed(() => { + const c1 = computed(() => { c1Spy() return src.value % 2 }) - const c2 = deferredComputed(() => { + const c2 = computed(() => { c2Spy() return c1.value + 1 }) @@ -145,11 +117,11 @@ describe('deferred computed', () => { const c2Spy = vi.fn() const src = ref(0) - const c1 = deferredComputed(() => { + const c1 = computed(() => { c1Spy() return src.value % 2 }) - const c2 = deferredComputed(() => { + const c2 = computed(() => { c2Spy() return c1.value + 1 }) @@ -162,14 +134,13 @@ describe('deferred computed', () => { src.value = 1 // sync access c2 c2.value - await tick expect(effectSpy).toHaveBeenCalledTimes(2) }) test('should not compute if deactivated before scheduler is called', async () => { const c1Spy = vi.fn() const src = ref(0) - const c1 = deferredComputed(() => { + const c1 = computed(() => { c1Spy() return src.value % 2 }) @@ -179,7 +150,6 @@ describe('deferred computed', () => { c1.effect.stop() // trigger src.value++ - await tick expect(c1Spy).toHaveBeenCalledTimes(1) }) }) diff --git a/packages/reactivity/__tests__/effect.spec.ts b/packages/reactivity/__tests__/effect.spec.ts index e673e570313..cf08ab99b2d 100644 --- a/packages/reactivity/__tests__/effect.spec.ts +++ b/packages/reactivity/__tests__/effect.spec.ts @@ -558,7 +558,7 @@ describe('reactivity/effect', () => { expect(output.fx2).toBe(1 + 3 + 3) expect(fx1Spy).toHaveBeenCalledTimes(1) - // Invoked once even change of fx1. + // Invoked due to change of fx1. expect(fx2Spy).toHaveBeenCalledTimes(1) fx1Spy.mockClear() diff --git a/packages/reactivity/__tests__/reactiveArray.spec.ts b/packages/reactivity/__tests__/reactiveArray.spec.ts index 8bc7ba1433a..d749a3d97f2 100644 --- a/packages/reactivity/__tests__/reactiveArray.spec.ts +++ b/packages/reactivity/__tests__/reactiveArray.spec.ts @@ -99,7 +99,7 @@ describe('reactivity/reactive/Array', () => { expect(fn).toHaveBeenCalledTimes(1) }) - test('shift on Array should only trigger dependency once', () => { + test('shift on Array should trigger dependency once', () => { const arr = reactive([1, 2, 3]) const fn = vi.fn() effect(() => { diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 7509d397d83..9dbc99afdd8 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -73,7 +73,7 @@ export class ComputedRefImpl { this._setter(newValue) } - // #region polyfill _dirty to backward compatibility for Vue <= 3.3 + // #region polyfill _dirty for backward compatibility with version <= 3.3.x get _dirty() { return this.effect.dirty } diff --git a/packages/reactivity/src/deferredComputed.ts b/packages/reactivity/src/deferredComputed.ts index 8c4b163d0ba..50c4ced0f8d 100644 --- a/packages/reactivity/src/deferredComputed.ts +++ b/packages/reactivity/src/deferredComputed.ts @@ -1,93 +1,6 @@ -import { Dep } from './dep' -import { ReactiveEffect } from './effect' -import { ComputedGetter, ComputedRef } from './computed' -import { ReactiveFlags, toRaw } from './reactive' -import { trackRefValue, triggerRefValue } from './ref' +import { computed } from './computed' -const tick = /*#__PURE__*/ Promise.resolve() -const queue: any[] = [] -let queued = false - -const scheduler = (fn: any) => { - queue.push(fn) - if (!queued) { - queued = true - tick.then(flush) - } -} - -const flush = () => { - for (let i = 0; i < queue.length; i++) { - queue[i]() - } - queue.length = 0 - queued = false -} - -class DeferredComputedRefImpl { - public dep?: Dep = undefined - - private _value!: T - private _dirty = true - public readonly effect: ReactiveEffect - - public readonly __v_isRef = true - public readonly [ReactiveFlags.IS_READONLY] = true - - constructor(getter: ComputedGetter) { - let compareTarget: any - let hasCompareTarget = false - let scheduled = false - this.effect = new ReactiveEffect( - getter, - (onScheduled, computedTrigger?: boolean) => { - if (this.dep) { - if (computedTrigger) { - compareTarget = this._value - hasCompareTarget = true - } else if (!scheduled) { - const valueToCompare = hasCompareTarget - ? compareTarget - : this._value - scheduled = true - hasCompareTarget = false - scheduler(() => { - if (this.effect.active && this._get() !== valueToCompare) { - triggerRefValue(this) - } - scheduled = false - }) - } - // chained upstream computeds are notified synchronously to ensure - // value invalidation in case of sync access; normal effects are - // deferred to be triggered in scheduler. - for (const e of this.dep) { - if (e.computed instanceof DeferredComputedRefImpl) { - e.scheduler(onScheduled, true /* computedTrigger */) - } - } - } - this._dirty = true - } - ) - this.effect.computed = this as any - } - - private _get() { - if (this._dirty) { - this._dirty = false - return (this._value = this.effect.run()!) - } - return this._value - } - - get value() { - trackRefValue(this) - // the computed ref may get wrapped by other proxies e.g. readonly() #3376 - return toRaw(this)._get() - } -} - -export function deferredComputed(getter: () => T): ComputedRef { - return new DeferredComputedRefImpl(getter) as any -} +/** + * @deprecated use `computed` instead. See #5912 + */ +export const deferredComputed = computed; diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index dbdfb506e37..030a3518d3c 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -100,7 +100,8 @@ export class ReactiveEffect { pauseTracking() for (const dep of this.deps) { if (dep.computed?._scheduled) { - triggerComputedGetter(dep.computed) // wrap with function call to avoid tree shaking + // wrap with function call to avoid tree shaking + triggerComputedGetter(dep.computed) if (this._dirtyLevel >= DirtyLevels.ComputedValueDirty) { break } From 9f6d9b668ded2fe92880f772a7fbc55da1201eeb Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sat, 9 Sep 2023 03:01:39 +0000 Subject: [PATCH 115/192] [autofix.ci] apply automated fixes --- packages/reactivity/src/deferredComputed.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reactivity/src/deferredComputed.ts b/packages/reactivity/src/deferredComputed.ts index 50c4ced0f8d..1dbba1f3f03 100644 --- a/packages/reactivity/src/deferredComputed.ts +++ b/packages/reactivity/src/deferredComputed.ts @@ -3,4 +3,4 @@ import { computed } from './computed' /** * @deprecated use `computed` instead. See #5912 */ -export const deferredComputed = computed; +export const deferredComputed = computed From 27a0b403024415986b95e6f31009d9af44af6b38 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 9 Sep 2023 11:19:17 +0800 Subject: [PATCH 116/192] simplify triggerEffects --- packages/reactivity/src/effect.ts | 54 +++++++++++-------------------- 1 file changed, 19 insertions(+), 35 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 030a3518d3c..10114b70f67 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -455,52 +455,36 @@ export function trigger( } } +const queueEffectCbs: (() => void)[] = [] +const pushEffectCb = queueEffectCbs.push.bind(queueEffectCbs) + export function triggerEffects( dep: Dep, dirtyLevel: DirtyLevels, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { pauseScheduling() - // spread into array for stabilization - const effects = [...dep] - for (const effect of effects) { - if (effect.computed) { - triggerEffect(effect, dirtyLevel, debuggerEventExtraInfo) - } - } - for (const effect of effects) { - if (!effect.computed) { - triggerEffect(effect, dirtyLevel, debuggerEventExtraInfo) + for (const effect of dep) { + if (effect !== activeEffect || effect.allowRecurse) { + if (__DEV__ && effect.onTrigger) { + effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) + } + if (effect._dirtyLevel < dirtyLevel) { + effect._dirtyLevel = dirtyLevel + } + if ( + dirtyLevel === DirtyLevels.ComputedValueMaybeDirty || + dirtyLevel === DirtyLevels.Dirty || + (dirtyLevel === DirtyLevels.ComputedValueDirty && + !effect._queryingDirty) + ) { + effect.scheduler(pushEffectCb) + } } } resetScheduling() } -const queueEffectCbs: (() => void)[] = [] -const pushEffectCb = queueEffectCbs.push.bind(queueEffectCbs) - -function triggerEffect( - effect: ReactiveEffect, - dirtyLevel: DirtyLevels, - debuggerEventExtraInfo?: DebuggerEventExtraInfo -) { - if (effect !== activeEffect || effect.allowRecurse) { - if (__DEV__ && effect.onTrigger) { - effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) - } - if (effect._dirtyLevel < dirtyLevel) { - effect._dirtyLevel = dirtyLevel - } - if ( - dirtyLevel === DirtyLevels.ComputedValueMaybeDirty || - dirtyLevel === DirtyLevels.Dirty || - (dirtyLevel === DirtyLevels.ComputedValueDirty && !effect._queryingDirty) - ) { - effect.scheduler(pushEffectCb) - } - } -} - export function getDepFromReactive(object: any, key: string | number | symbol) { return targetMap.get(object)?.get(key) } From 0147f1dee3446e710374bf7d27374b92bb876ca9 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 9 Sep 2023 11:50:00 +0800 Subject: [PATCH 117/192] `dep.computed` -> `dep.triggerDirty()` --- packages/reactivity/src/computed.ts | 2 +- packages/reactivity/src/dep.ts | 7 +++---- packages/reactivity/src/effect.ts | 13 +++--------- packages/reactivity/src/ref.ts | 31 +++++++++++++++++++---------- 4 files changed, 27 insertions(+), 26 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 9dbc99afdd8..00af66f18da 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -56,7 +56,7 @@ export class ComputedRefImpl { get value() { // the computed ref may get wrapped by other proxies e.g. readonly() #3376 const self = toRaw(this) - trackRefValue(self, self) + trackRefValue(self, true) if (!self._cacheable || self.effect.dirty) { const newValue = self.effect.run()! const changed = hasChanged(self._value, newValue) diff --git a/packages/reactivity/src/dep.ts b/packages/reactivity/src/dep.ts index 4693a0cb057..ba018dccdbb 100644 --- a/packages/reactivity/src/dep.ts +++ b/packages/reactivity/src/dep.ts @@ -1,4 +1,3 @@ -import type { ComputedRefImpl } from './computed' import { ReactiveEffect, trackOpBit } from './effect' export type Dep = Set & TrackedMarkers @@ -17,17 +16,17 @@ type TrackedMarkers = { * newTracked */ n: number - computed?: ComputedRefImpl + queryDirty?: () => void } export const createDep = ( effects?: ReactiveEffect[], - computed?: ComputedRefImpl + triggerDirty?: () => void ): Dep => { const dep = new Set(effects) as Dep dep.w = 0 dep.n = 0 - dep.computed = computed + dep.queryDirty = triggerDirty return dep } diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 10114b70f67..41803af9ad1 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -53,10 +53,6 @@ export let activeEffect: ReactiveEffect | undefined export const ITERATE_KEY = Symbol(__DEV__ ? 'iterate' : '') export const MAP_KEY_ITERATE_KEY = Symbol(__DEV__ ? 'Map key iterate' : '') -function triggerComputedGetter(computed: ComputedRefImpl) { - return computed.value -} - export class ReactiveEffect { active = true deps: Dep[] = [] @@ -99,12 +95,9 @@ export class ReactiveEffect { this._queryingDirty = true pauseTracking() for (const dep of this.deps) { - if (dep.computed?._scheduled) { - // wrap with function call to avoid tree shaking - triggerComputedGetter(dep.computed) - if (this._dirtyLevel >= DirtyLevels.ComputedValueDirty) { - break - } + dep.queryDirty?.() + if (this._dirtyLevel >= DirtyLevels.ComputedValueDirty) { + break } } resetTracking() diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 1d789353064..c5a314d5e93 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -18,7 +18,6 @@ import { import type { ShallowReactiveMarker } from './reactive' import { CollectionTypes } from './collectionHandlers' import { createDep, Dep } from './dep' -import type { ComputedRefImpl } from './computed' declare const RefSymbol: unique symbol export declare const RawSymbol: unique symbol @@ -38,20 +37,30 @@ type RefBase = { value: T } -export function trackRefValue( - ref: RefBase, - computed?: ComputedRefImpl -) { +export function trackRefValue(ref: RefBase, isComputed?: boolean) { if (shouldTrack && activeEffect) { ref = toRaw(ref) if (__DEV__) { - trackEffects(ref.dep || (ref.dep = createDep(undefined, computed)), { - target: ref, - type: TrackOpTypes.GET, - key: 'value' - }) + trackEffects( + ref.dep || + (ref.dep = createDep( + undefined, + isComputed ? () => ref.value : undefined + )), + { + target: ref, + type: TrackOpTypes.GET, + key: 'value' + } + ) } else { - trackEffects(ref.dep || (ref.dep = createDep(undefined, computed))) + trackEffects( + ref.dep || + (ref.dep = createDep( + undefined, + isComputed ? () => ref.value : undefined + )) + ) } } } From f4b13db568d3962935dc2150120b0aa9307601ec Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 9 Sep 2023 11:52:20 +0800 Subject: [PATCH 118/192] Update dep.ts --- packages/reactivity/src/dep.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/reactivity/src/dep.ts b/packages/reactivity/src/dep.ts index ba018dccdbb..90857a90f1a 100644 --- a/packages/reactivity/src/dep.ts +++ b/packages/reactivity/src/dep.ts @@ -21,12 +21,12 @@ type TrackedMarkers = { export const createDep = ( effects?: ReactiveEffect[], - triggerDirty?: () => void + queryDirty?: () => void ): Dep => { const dep = new Set(effects) as Dep dep.w = 0 dep.n = 0 - dep.queryDirty = triggerDirty + dep.queryDirty = queryDirty return dep } From ee6fccada620c2587dc38eb1e7be99458259f2e7 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 9 Sep 2023 12:43:57 +0800 Subject: [PATCH 119/192] update test title --- packages/runtime-core/__tests__/apiWatch.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index 7ead1011e25..4226a8d8ee3 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -1206,7 +1206,7 @@ describe('api: watch', () => { expect(countW).toBe(2) }) - it('watch callback on-demand trigger', () => { + it('should not trigger if computed value did not change', () => { const effectSpy = vi.fn() const sec = ref(0) From 7c59d8ee38da3f19507e157b97a2951b7bf1096e Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 9 Sep 2023 12:54:58 +0800 Subject: [PATCH 120/192] remove async --- .../reactivity/__tests__/deferredComputed.spec.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/reactivity/__tests__/deferredComputed.spec.ts b/packages/reactivity/__tests__/deferredComputed.spec.ts index 688cdb58aba..a4083b8de0a 100644 --- a/packages/reactivity/__tests__/deferredComputed.spec.ts +++ b/packages/reactivity/__tests__/deferredComputed.spec.ts @@ -1,7 +1,7 @@ import { computed, effect, ref } from '../src' describe('deferred computed', () => { - test('should not trigger if value did not change', async () => { + test('should not trigger if value did not change', () => { const src = ref(0) const c = computed(() => src.value % 2) const spy = vi.fn() @@ -19,7 +19,7 @@ describe('deferred computed', () => { expect(spy).toHaveBeenCalledTimes(2) }) - test('chained computed trigger', async () => { + test('chained computed trigger', () => { const effectSpy = vi.fn() const c1Spy = vi.fn() const c2Spy = vi.fn() @@ -48,7 +48,7 @@ describe('deferred computed', () => { expect(effectSpy).toHaveBeenCalledTimes(2) }) - test('chained computed avoid re-compute', async () => { + test('chained computed avoid re-compute', () => { const effectSpy = vi.fn() const c1Spy = vi.fn() const c2Spy = vi.fn() @@ -79,7 +79,7 @@ describe('deferred computed', () => { expect(effectSpy).toHaveBeenCalledTimes(1) }) - test('chained computed value invalidation', async () => { + test('chained computed value invalidation', () => { const effectSpy = vi.fn() const c1Spy = vi.fn() const c2Spy = vi.fn() @@ -111,7 +111,7 @@ describe('deferred computed', () => { expect(c2Spy).toHaveBeenCalledTimes(2) }) - test('sync access of invalidated chained computed should not prevent final effect from running', async () => { + test('sync access of invalidated chained computed should not prevent final effect from running', () => { const effectSpy = vi.fn() const c1Spy = vi.fn() const c2Spy = vi.fn() @@ -137,7 +137,7 @@ describe('deferred computed', () => { expect(effectSpy).toHaveBeenCalledTimes(2) }) - test('should not compute if deactivated before scheduler is called', async () => { + test('should not compute if deactivated before scheduler is called', () => { const c1Spy = vi.fn() const src = ref(0) const c1 = computed(() => { From fc326b2a24466aa21654d5f691d456ded302e164 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 9 Sep 2023 16:42:28 +0800 Subject: [PATCH 121/192] perf: check _scheduled --- packages/reactivity/src/computed.ts | 2 +- packages/reactivity/src/ref.ts | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 00af66f18da..9dbc99afdd8 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -56,7 +56,7 @@ export class ComputedRefImpl { get value() { // the computed ref may get wrapped by other proxies e.g. readonly() #3376 const self = toRaw(this) - trackRefValue(self, true) + trackRefValue(self, self) if (!self._cacheable || self.effect.dirty) { const newValue = self.effect.run()! const changed = hasChanged(self._value, newValue) diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index c5a314d5e93..1a97c1c698c 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -18,6 +18,7 @@ import { import type { ShallowReactiveMarker } from './reactive' import { CollectionTypes } from './collectionHandlers' import { createDep, Dep } from './dep' +import type { ComputedRefImpl } from './computed' declare const RefSymbol: unique symbol export declare const RawSymbol: unique symbol @@ -37,7 +38,10 @@ type RefBase = { value: T } -export function trackRefValue(ref: RefBase, isComputed?: boolean) { +export function trackRefValue( + ref: RefBase, + computed?: ComputedRefImpl +) { if (shouldTrack && activeEffect) { ref = toRaw(ref) if (__DEV__) { @@ -45,7 +49,7 @@ export function trackRefValue(ref: RefBase, isComputed?: boolean) { ref.dep || (ref.dep = createDep( undefined, - isComputed ? () => ref.value : undefined + computed ? () => computed._scheduled && computed.value : undefined )), { target: ref, @@ -58,7 +62,7 @@ export function trackRefValue(ref: RefBase, isComputed?: boolean) { ref.dep || (ref.dep = createDep( undefined, - isComputed ? () => ref.value : undefined + computed ? () => computed._scheduled && computed.value : undefined )) ) } From e26976f81fbf362af49d20d46f41dadd5bd1f475 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 9 Sep 2023 17:27:58 +0800 Subject: [PATCH 122/192] refactor: operations.ts -> constants.ts --- packages/reactivity/src/baseHandlers.ts | 3 +-- packages/reactivity/src/collectionHandlers.ts | 4 ++-- packages/reactivity/src/computed.ts | 6 +++--- .../src/{operations.ts => constants.ts} | 8 ++++++++ packages/reactivity/src/effect.ts | 2 +- packages/reactivity/src/index.ts | 6 +++--- packages/reactivity/src/reactive.ts | 9 +-------- packages/reactivity/src/ref.ts | 17 +++++++++-------- 8 files changed, 28 insertions(+), 27 deletions(-) rename packages/reactivity/src/{operations.ts => constants.ts} (69%) diff --git a/packages/reactivity/src/baseHandlers.ts b/packages/reactivity/src/baseHandlers.ts index 264ba36cf83..3f7253c3dc8 100644 --- a/packages/reactivity/src/baseHandlers.ts +++ b/packages/reactivity/src/baseHandlers.ts @@ -2,7 +2,6 @@ import { reactive, readonly, toRaw, - ReactiveFlags, Target, readonlyMap, reactiveMap, @@ -11,7 +10,7 @@ import { isReadonly, isShallow } from './reactive' -import { TrackOpTypes, TriggerOpTypes } from './operations' +import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants' import { track, trigger, diff --git a/packages/reactivity/src/collectionHandlers.ts b/packages/reactivity/src/collectionHandlers.ts index 1d07af3be8c..a9500b19bdb 100644 --- a/packages/reactivity/src/collectionHandlers.ts +++ b/packages/reactivity/src/collectionHandlers.ts @@ -1,6 +1,6 @@ -import { toRaw, ReactiveFlags, toReactive, toReadonly } from './reactive' +import { toRaw, toReactive, toReadonly } from './reactive' import { track, trigger, ITERATE_KEY, MAP_KEY_ITERATE_KEY } from './effect' -import { TrackOpTypes, TriggerOpTypes } from './operations' +import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants' import { capitalize, hasOwn, hasChanged, toRawType, isMap } from '@vue/shared' export type CollectionTypes = IterableCollections | WeakCollections diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 9dbc99afdd8..1fe25e26fab 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -1,9 +1,9 @@ import { DebuggerOptions, ReactiveEffect } from './effect' import { Ref, trackRefValue, triggerRefValue } from './ref' import { hasChanged, isFunction, NOOP } from '@vue/shared' -import { ReactiveFlags, toRaw } from './reactive' +import { toRaw } from './reactive' import { Dep } from './dep' -import { DirtyLevels } from './operations' +import { DirtyLevels, ReactiveFlags } from './constants' declare const ComputedRefSymbol: unique symbol @@ -56,7 +56,7 @@ export class ComputedRefImpl { get value() { // the computed ref may get wrapped by other proxies e.g. readonly() #3376 const self = toRaw(this) - trackRefValue(self, self) + trackRefValue(self) if (!self._cacheable || self.effect.dirty) { const newValue = self.effect.run()! const changed = hasChanged(self._value, newValue) diff --git a/packages/reactivity/src/operations.ts b/packages/reactivity/src/constants.ts similarity index 69% rename from packages/reactivity/src/operations.ts rename to packages/reactivity/src/constants.ts index 26b231e3dc6..4ad2ec3c7da 100644 --- a/packages/reactivity/src/operations.ts +++ b/packages/reactivity/src/constants.ts @@ -14,6 +14,14 @@ export const enum TriggerOpTypes { CLEAR = 'clear' } +export const enum ReactiveFlags { + SKIP = '__v_skip', + IS_REACTIVE = '__v_isReactive', + IS_READONLY = '__v_isReadonly', + IS_SHALLOW = '__v_isShallow', + RAW = '__v_raw' +} + export const enum DirtyLevels { NotDirty = 0, ComputedValueMaybeDirty = 1, diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 41803af9ad1..b352c101c3f 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -1,4 +1,4 @@ -import { DirtyLevels, TrackOpTypes, TriggerOpTypes } from './operations' +import { DirtyLevels, TrackOpTypes, TriggerOpTypes } from './constants' import { extend, isArray, isIntegerKey, isMap } from '@vue/shared' import { EffectScope, recordEffectScope } from './effectScope' import { diff --git a/packages/reactivity/src/index.ts b/packages/reactivity/src/index.ts index ee4da5b1935..6e29c22f681 100644 --- a/packages/reactivity/src/index.ts +++ b/packages/reactivity/src/index.ts @@ -31,7 +31,6 @@ export { shallowReadonly, markRaw, toRaw, - ReactiveFlags /* @remove */, type Raw, type DeepReadonly, type ShallowReactive, @@ -71,5 +70,6 @@ export { } from './effectScope' export { TrackOpTypes /* @remove */, - TriggerOpTypes /* @remove */ -} from './operations' + TriggerOpTypes /* @remove */, + ReactiveFlags /* @remove */ +} from './constants' diff --git a/packages/reactivity/src/reactive.ts b/packages/reactivity/src/reactive.ts index 1881955cf1c..2904c69abe2 100644 --- a/packages/reactivity/src/reactive.ts +++ b/packages/reactivity/src/reactive.ts @@ -12,14 +12,7 @@ import { shallowReadonlyCollectionHandlers } from './collectionHandlers' import type { UnwrapRefSimple, Ref, RawSymbol } from './ref' - -export const enum ReactiveFlags { - SKIP = '__v_skip', - IS_REACTIVE = '__v_isReactive', - IS_READONLY = '__v_isReadonly', - IS_SHALLOW = '__v_isShallow', - RAW = '__v_raw' -} +import { ReactiveFlags } from './constants' export interface Target { [ReactiveFlags.SKIP]?: boolean diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 1a97c1c698c..29f4de40087 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -5,7 +5,7 @@ import { trackEffects, triggerEffects } from './effect' -import { DirtyLevels, TrackOpTypes, TriggerOpTypes } from './operations' +import { DirtyLevels, TrackOpTypes, TriggerOpTypes } from './constants' import { isArray, hasChanged, IfAny, isFunction, isObject } from '@vue/shared' import { isProxy, @@ -18,7 +18,7 @@ import { import type { ShallowReactiveMarker } from './reactive' import { CollectionTypes } from './collectionHandlers' import { createDep, Dep } from './dep' -import type { ComputedRefImpl } from './computed' +import { ComputedRefImpl } from './computed' declare const RefSymbol: unique symbol export declare const RawSymbol: unique symbol @@ -38,10 +38,7 @@ type RefBase = { value: T } -export function trackRefValue( - ref: RefBase, - computed?: ComputedRefImpl -) { +export function trackRefValue(ref: RefBase) { if (shouldTrack && activeEffect) { ref = toRaw(ref) if (__DEV__) { @@ -49,7 +46,9 @@ export function trackRefValue( ref.dep || (ref.dep = createDep( undefined, - computed ? () => computed._scheduled && computed.value : undefined + ref instanceof ComputedRefImpl + ? () => (ref as ComputedRefImpl)._scheduled && ref.value + : undefined )), { target: ref, @@ -62,7 +61,9 @@ export function trackRefValue( ref.dep || (ref.dep = createDep( undefined, - computed ? () => computed._scheduled && computed.value : undefined + ref instanceof ComputedRefImpl + ? () => (ref as ComputedRefImpl)._scheduled && ref.value + : undefined )) ) } From a0b4fcb67efcbe5a294d1bdf4863fb0e206cf5d0 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 19 Sep 2023 14:04:43 +0800 Subject: [PATCH 123/192] add test for pauseScheduling --- packages/reactivity/__tests__/effect.spec.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/reactivity/__tests__/effect.spec.ts b/packages/reactivity/__tests__/effect.spec.ts index 05f93feee51..01b273d57ba 100644 --- a/packages/reactivity/__tests__/effect.spec.ts +++ b/packages/reactivity/__tests__/effect.spec.ts @@ -12,7 +12,7 @@ import { readonly, ReactiveEffectRunner } from '../src/index' -import { ITERATE_KEY } from '../src/effect' +import { ITERATE_KEY, pauseScheduling, resetScheduling } from '../src/effect' describe('reactivity/effect', () => { it('should run the passed function once (wrapped by a effect)', () => { @@ -999,4 +999,19 @@ describe('reactivity/effect', () => { expect(has).toBe(false) }) }) + + it('should be triggered once when with pauseScheduling', () => { + const counter = reactive({ num: 0 }) + + const counterSpy = vi.fn(() => counter.num) + effect(counterSpy) + + counterSpy.mockClear() + + pauseScheduling() + counter.num++ + counter.num++ + resetScheduling() + expect(counterSpy).toHaveBeenCalledTimes(1) + }) }) From 59783113202ed17644bcdba1dbac244f681e2eda Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 19 Sep 2023 14:05:18 +0800 Subject: [PATCH 124/192] expose pauseScheduling, resetScheduling --- packages/reactivity/src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/reactivity/src/index.ts b/packages/reactivity/src/index.ts index 6e29c22f681..a2059c25604 100644 --- a/packages/reactivity/src/index.ts +++ b/packages/reactivity/src/index.ts @@ -53,6 +53,8 @@ export { enableTracking, pauseTracking, resetTracking, + pauseScheduling, + resetScheduling, ITERATE_KEY, ReactiveEffect, type ReactiveEffectRunner, From 52ac1ab3ae79014bd9565d72dbefbd9d0cd2c831 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 19 Sep 2023 18:01:50 +0800 Subject: [PATCH 125/192] chore: fix test message --- packages/reactivity/__tests__/effect.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reactivity/__tests__/effect.spec.ts b/packages/reactivity/__tests__/effect.spec.ts index 01b273d57ba..a78732eed2d 100644 --- a/packages/reactivity/__tests__/effect.spec.ts +++ b/packages/reactivity/__tests__/effect.spec.ts @@ -1000,7 +1000,7 @@ describe('reactivity/effect', () => { }) }) - it('should be triggered once when with pauseScheduling', () => { + it('should be triggered once with pauseScheduling', () => { const counter = reactive({ num: 0 }) const counterSpy = vi.fn(() => counter.num) From c44cb562f5adcb9c7378e68b9a3e155e3f2bc926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=B6=E8=BF=9C=E6=96=B9?= Date: Wed, 30 Aug 2023 15:25:51 +0800 Subject: [PATCH 126/192] feat(runtime-core): add `once` option to watch (#9034) --- .../runtime-core/__tests__/apiWatch.spec.ts | 38 +++++++++++++++++++ packages/runtime-core/src/apiWatch.ts | 31 +++++++++++---- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index f24ce80b9df..bddfc5ff541 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -1205,4 +1205,42 @@ describe('api: watch', () => { expect(countWE).toBe(3) expect(countW).toBe(2) }) + + const options = [ + { name: 'only trigger once watch' }, + { + deep: true, + name: 'only trigger once watch with deep' + }, + { + flush: 'sync', + name: 'only trigger once watch with flush: sync' + }, + { + flush: 'pre', + name: 'only trigger once watch with flush: pre' + }, + { + immediate: true, + name: 'only trigger once watch with immediate' + } + ] as const + test.each(options)('$name', async option => { + const count = ref(0) + const cb = vi.fn() + + watch(count, cb, { once: true, ...option }) + + count.value++ + await nextTick() + + expect(count.value).toBe(1) + expect(cb).toHaveBeenCalledTimes(1) + + count.value++ + await nextTick() + + expect(count.value).toBe(2) + expect(cb).toHaveBeenCalledTimes(1) + }) }) diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 1b85ba12d19..c307c4198a3 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -75,6 +75,7 @@ export interface WatchOptionsBase extends DebuggerOptions { export interface WatchOptions extends WatchOptionsBase { immediate?: Immediate deep?: boolean + once?: boolean } export type WatchStopHandle = () => void @@ -172,8 +173,16 @@ export function watch = false>( function doWatch( source: WatchSource | WatchSource[] | WatchEffect | object, cb: WatchCallback | null, - { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ + { immediate, deep, flush, once, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ ): WatchStopHandle { + if (cb && once) { + const _cb = cb + cb = (...args) => { + _cb(...args) + unwatch() + } + } + if (__DEV__ && !cb) { if (immediate !== undefined) { warn( @@ -187,6 +196,12 @@ function doWatch( `watch(source, callback, options?) signature.` ) } + if (once !== undefined) { + warn( + `watch() "once" option is only respected when using the ` + + `watch(source, callback, options?) signature.` + ) + } } const warnInvalidSource = (s: unknown) => { @@ -363,6 +378,13 @@ function doWatch( const effect = new ReactiveEffect(getter, scheduler) + const unwatch = () => { + effect.stop() + if (instance && instance.scope) { + remove(instance.scope.effects!, effect) + } + } + if (__DEV__) { effect.onTrack = onTrack effect.onTrigger = onTrigger @@ -384,13 +406,6 @@ function doWatch( effect.run() } - const unwatch = () => { - effect.stop() - if (instance && instance.scope) { - remove(instance.scope.effects!, effect) - } - } - if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch) return unwatch } From 7bc3ab170264edac5cc9705d66b811e767424e23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Mon, 4 Sep 2023 03:59:11 -0500 Subject: [PATCH 127/192] feat(compiler-sfc): expose resolve type-based props and emits (#8874) --- packages/compiler-sfc/src/index.ts | 3 +++ .../compiler-sfc/src/script/defineEmits.ts | 10 ++++--- .../compiler-sfc/src/script/defineProps.ts | 22 ++++++++++------ .../compiler-sfc/src/script/resolveType.ts | 26 +++++++++++++++++-- 4 files changed, 48 insertions(+), 13 deletions(-) diff --git a/packages/compiler-sfc/src/index.ts b/packages/compiler-sfc/src/index.ts index 76b4900d46d..c6ee604146e 100644 --- a/packages/compiler-sfc/src/index.ts +++ b/packages/compiler-sfc/src/index.ts @@ -33,6 +33,8 @@ export { // Internals for type resolution export { invalidateTypeCache, registerTS } from './script/resolveType' +export { extractRuntimeProps } from './script/defineProps' +export { extractRuntimeEmits } from './script/defineEmits' // Types export type { @@ -58,6 +60,7 @@ export type { SFCScriptCompileOptions } from './compileScript' export type { ScriptCompileContext } from './script/context' export type { TypeResolveContext, + SimpleTypeResolveOptions, SimpleTypeResolveContext } from './script/resolveType' export type { diff --git a/packages/compiler-sfc/src/script/defineEmits.ts b/packages/compiler-sfc/src/script/defineEmits.ts index 8bd4cdfe543..b7453076cfe 100644 --- a/packages/compiler-sfc/src/script/defineEmits.ts +++ b/packages/compiler-sfc/src/script/defineEmits.ts @@ -8,7 +8,11 @@ import { } from '@babel/types' import { isCallOf } from './utils' import { ScriptCompileContext } from './context' -import { resolveTypeElements, resolveUnionType } from './resolveType' +import { + TypeResolveContext, + resolveTypeElements, + resolveUnionType +} from './resolveType' export const DEFINE_EMITS = 'defineEmits' @@ -64,7 +68,7 @@ export function genRuntimeEmits(ctx: ScriptCompileContext): string | undefined { return emitsDecl } -function extractRuntimeEmits(ctx: ScriptCompileContext): Set { +export function extractRuntimeEmits(ctx: TypeResolveContext): Set { const emits = new Set() const node = ctx.emitsTypeDecl! @@ -97,7 +101,7 @@ function extractRuntimeEmits(ctx: ScriptCompileContext): Set { } function extractEventNames( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, eventName: ArrayPattern | Identifier | ObjectPattern | RestElement, emits: Set ) { diff --git a/packages/compiler-sfc/src/script/defineProps.ts b/packages/compiler-sfc/src/script/defineProps.ts index 5004e314da1..449ed250d1d 100644 --- a/packages/compiler-sfc/src/script/defineProps.ts +++ b/packages/compiler-sfc/src/script/defineProps.ts @@ -8,7 +8,11 @@ import { } from '@babel/types' import { BindingTypes, isFunctionType } from '@vue/compiler-dom' import { ScriptCompileContext } from './context' -import { inferRuntimeType, resolveTypeElements } from './resolveType' +import { + TypeResolveContext, + inferRuntimeType, + resolveTypeElements +} from './resolveType' import { resolveObjectKey, UNKNOWN_TYPE, @@ -150,7 +154,7 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined { } } } else if (ctx.propsTypeDecl) { - propsDecls = genRuntimePropsFromTypes(ctx) + propsDecls = extractRuntimeProps(ctx) } const modelsDecls = genModelProps(ctx) @@ -162,7 +166,9 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined { } } -function genRuntimePropsFromTypes(ctx: ScriptCompileContext) { +export function extractRuntimeProps( + ctx: TypeResolveContext +): string | undefined { // this is only called if propsTypeDecl exists const props = resolveRuntimePropsFromType(ctx, ctx.propsTypeDecl!) if (!props.length) { @@ -175,7 +181,7 @@ function genRuntimePropsFromTypes(ctx: ScriptCompileContext) { for (const prop of props) { propStrings.push(genRuntimePropFromType(ctx, prop, hasStaticDefaults)) // register bindings - if (!(prop.key in ctx.bindingMetadata)) { + if ('bindingMetadata' in ctx && !(prop.key in ctx.bindingMetadata)) { ctx.bindingMetadata[prop.key] = BindingTypes.PROPS } } @@ -193,7 +199,7 @@ function genRuntimePropsFromTypes(ctx: ScriptCompileContext) { } function resolveRuntimePropsFromType( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, node: Node ): PropTypeData[] { const props: PropTypeData[] = [] @@ -222,7 +228,7 @@ function resolveRuntimePropsFromType( } function genRuntimePropFromType( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, { key, required, type, skipCheck }: PropTypeData, hasStaticDefaults: boolean ): string { @@ -284,7 +290,7 @@ function genRuntimePropFromType( * static properties, we can directly generate more optimized default * declarations. Otherwise we will have to fallback to runtime merging. */ -function hasStaticWithDefaults(ctx: ScriptCompileContext) { +function hasStaticWithDefaults(ctx: TypeResolveContext) { return !!( ctx.propsRuntimeDefaults && ctx.propsRuntimeDefaults.type === 'ObjectExpression' && @@ -297,7 +303,7 @@ function hasStaticWithDefaults(ctx: ScriptCompileContext) { } function genDestructuredDefaultValue( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, key: string, inferredType?: string[] ): diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index 78581432366..b298f9a8e82 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -42,6 +42,13 @@ import type TS from 'typescript' import { extname, dirname } from 'path' import { minimatch as isMatch } from 'minimatch' +export type SimpleTypeResolveOptions = Partial< + Pick< + SFCScriptCompileOptions, + 'globalTypeFiles' | 'fs' | 'babelParserPlugins' | 'isProd' + > +> + /** * TypeResolveContext is compatible with ScriptCompileContext * but also allows a simpler version of it with minimal required properties @@ -59,13 +66,28 @@ import { minimatch as isMatch } from 'minimatch' */ export type SimpleTypeResolveContext = Pick< ScriptCompileContext, - // required - 'source' | 'filename' | 'error' | 'options' + // file + | 'source' + | 'filename' + + // utils + | 'error' + | 'helper' + | 'getString' + + // props + | 'propsTypeDecl' + | 'propsRuntimeDefaults' + | 'propsDestructuredBindings' + + // emits + | 'emitsTypeDecl' > & Partial< Pick > & { ast: Statement[] + options: SimpleTypeResolveOptions } export type TypeResolveContext = ScriptCompileContext | SimpleTypeResolveContext From dc30885eb9d7e62797f907b1458c3b5994575528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8A=B1=E6=9E=9C=E5=B1=B1=E5=A4=A7=E5=9C=A3?= <316783812@qq.com> Date: Tue, 5 Sep 2023 15:55:39 +0800 Subject: [PATCH 128/192] feat(compiler-core): export error message (#8729) --- packages/compiler-core/src/index.ts | 1 + packages/compiler-dom/src/index.ts | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/compiler-core/src/index.ts b/packages/compiler-core/src/index.ts index 4898a181dfc..588bb92cc5f 100644 --- a/packages/compiler-core/src/index.ts +++ b/packages/compiler-core/src/index.ts @@ -24,6 +24,7 @@ export { export { generate, type CodegenContext, type CodegenResult } from './codegen' export { ErrorCodes, + errorMessages, createCompilerError, type CoreCompilerError, type CompilerError diff --git a/packages/compiler-dom/src/index.ts b/packages/compiler-dom/src/index.ts index 2c6f71cefbb..a2f4aff2e4c 100644 --- a/packages/compiler-dom/src/index.ts +++ b/packages/compiler-dom/src/index.ts @@ -68,5 +68,9 @@ export function parse(template: string, options: ParserOptions = {}): RootNode { export * from './runtimeHelpers' export { transformStyle } from './transforms/transformStyle' -export { createDOMCompilerError, DOMErrorCodes } from './errors' +export { + createDOMCompilerError, + DOMErrorCodes, + DOMErrorMessages +} from './errors' export * from '@vue/compiler-core' From f2bb0ea7a56178fa294fdd3932b03724b4920211 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Fri, 22 Sep 2023 15:21:07 +0800 Subject: [PATCH 129/192] chore: mark `@internal` for pauseScheduling, resetScheduling --- packages/reactivity/src/effect.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 78bdd3c3673..410936c7a49 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -279,11 +279,17 @@ export function resetTracking() { shouldTrack = last === undefined ? true : last } +/** + * @internal + */ export function pauseScheduling() { scheduleStack.push(shouldSchedule) shouldSchedule = false } +/** + * @internal + */ export function resetScheduling() { const last = scheduleStack.pop() shouldSchedule = last === undefined ? true : last From 196cdd3eb8fa1b5e363fb3425dc454f9fb33dc2c Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Fri, 22 Sep 2023 15:30:28 +0800 Subject: [PATCH 130/192] Update packages/reactivity/src/computed.ts Co-authored-by: edison --- packages/reactivity/src/computed.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 1fe25e26fab..c9126d0769e 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -60,7 +60,8 @@ export class ComputedRefImpl { if (!self._cacheable || self.effect.dirty) { const newValue = self.effect.run()! const changed = hasChanged(self._value, newValue) - self._value = newValue +if(changed) { + self._value = newValue if (changed) { triggerRefValue(self, DirtyLevels.ComputedValueDirty) } From e6ee9f3abc49eb0ca6d0cb9b28eee85c49213e1a Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Fri, 22 Sep 2023 15:32:07 +0800 Subject: [PATCH 131/192] fixs https://github.com/vuejs/core/pull/5912/commits/196cdd3eb8fa1b5e363fb3425dc454f9fb33dc2c --- packages/reactivity/src/computed.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index c9126d0769e..22d930bed98 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -59,10 +59,8 @@ export class ComputedRefImpl { trackRefValue(self) if (!self._cacheable || self.effect.dirty) { const newValue = self.effect.run()! - const changed = hasChanged(self._value, newValue) -if(changed) { - self._value = newValue - if (changed) { + if (hasChanged(self._value, newValue)) { + self._value = newValue triggerRefValue(self, DirtyLevels.ComputedValueDirty) } self._scheduled = false From f5e93b7a95da72f23bdb2d27476f5b4b21113375 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Fri, 22 Sep 2023 15:36:54 +0800 Subject: [PATCH 132/192] Update computed.ts --- packages/reactivity/src/computed.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 22d930bed98..0467f7a1564 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -58,9 +58,7 @@ export class ComputedRefImpl { const self = toRaw(this) trackRefValue(self) if (!self._cacheable || self.effect.dirty) { - const newValue = self.effect.run()! - if (hasChanged(self._value, newValue)) { - self._value = newValue + if (hasChanged(self._value, (self._value = self.effect.run()!))) { triggerRefValue(self, DirtyLevels.ComputedValueDirty) } self._scheduled = false From d573e50e3de3aa0e3ffea2861da9a7b4e6e28aa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=B6=E8=BF=9C=E6=96=B9?= Date: Wed, 30 Aug 2023 15:25:51 +0800 Subject: [PATCH 133/192] feat(runtime-core): add `once` option to watch (#9034) --- .../runtime-core/__tests__/apiWatch.spec.ts | 38 +++++++++++++++++++ packages/runtime-core/src/apiWatch.ts | 31 +++++++++++---- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index f24ce80b9df..bddfc5ff541 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -1205,4 +1205,42 @@ describe('api: watch', () => { expect(countWE).toBe(3) expect(countW).toBe(2) }) + + const options = [ + { name: 'only trigger once watch' }, + { + deep: true, + name: 'only trigger once watch with deep' + }, + { + flush: 'sync', + name: 'only trigger once watch with flush: sync' + }, + { + flush: 'pre', + name: 'only trigger once watch with flush: pre' + }, + { + immediate: true, + name: 'only trigger once watch with immediate' + } + ] as const + test.each(options)('$name', async option => { + const count = ref(0) + const cb = vi.fn() + + watch(count, cb, { once: true, ...option }) + + count.value++ + await nextTick() + + expect(count.value).toBe(1) + expect(cb).toHaveBeenCalledTimes(1) + + count.value++ + await nextTick() + + expect(count.value).toBe(2) + expect(cb).toHaveBeenCalledTimes(1) + }) }) diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 1b85ba12d19..c307c4198a3 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -75,6 +75,7 @@ export interface WatchOptionsBase extends DebuggerOptions { export interface WatchOptions extends WatchOptionsBase { immediate?: Immediate deep?: boolean + once?: boolean } export type WatchStopHandle = () => void @@ -172,8 +173,16 @@ export function watch = false>( function doWatch( source: WatchSource | WatchSource[] | WatchEffect | object, cb: WatchCallback | null, - { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ + { immediate, deep, flush, once, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ ): WatchStopHandle { + if (cb && once) { + const _cb = cb + cb = (...args) => { + _cb(...args) + unwatch() + } + } + if (__DEV__ && !cb) { if (immediate !== undefined) { warn( @@ -187,6 +196,12 @@ function doWatch( `watch(source, callback, options?) signature.` ) } + if (once !== undefined) { + warn( + `watch() "once" option is only respected when using the ` + + `watch(source, callback, options?) signature.` + ) + } } const warnInvalidSource = (s: unknown) => { @@ -363,6 +378,13 @@ function doWatch( const effect = new ReactiveEffect(getter, scheduler) + const unwatch = () => { + effect.stop() + if (instance && instance.scope) { + remove(instance.scope.effects!, effect) + } + } + if (__DEV__) { effect.onTrack = onTrack effect.onTrigger = onTrigger @@ -384,13 +406,6 @@ function doWatch( effect.run() } - const unwatch = () => { - effect.stop() - if (instance && instance.scope) { - remove(instance.scope.effects!, effect) - } - } - if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch) return unwatch } From d52617fc15fcb48a47a12e0d7f57d34a2cc6bcb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Mon, 4 Sep 2023 03:59:11 -0500 Subject: [PATCH 134/192] feat(compiler-sfc): expose resolve type-based props and emits (#8874) --- packages/compiler-sfc/src/index.ts | 3 +++ .../compiler-sfc/src/script/defineEmits.ts | 10 ++++--- .../compiler-sfc/src/script/defineProps.ts | 22 ++++++++++------ .../compiler-sfc/src/script/resolveType.ts | 26 +++++++++++++++++-- 4 files changed, 48 insertions(+), 13 deletions(-) diff --git a/packages/compiler-sfc/src/index.ts b/packages/compiler-sfc/src/index.ts index 76b4900d46d..c6ee604146e 100644 --- a/packages/compiler-sfc/src/index.ts +++ b/packages/compiler-sfc/src/index.ts @@ -33,6 +33,8 @@ export { // Internals for type resolution export { invalidateTypeCache, registerTS } from './script/resolveType' +export { extractRuntimeProps } from './script/defineProps' +export { extractRuntimeEmits } from './script/defineEmits' // Types export type { @@ -58,6 +60,7 @@ export type { SFCScriptCompileOptions } from './compileScript' export type { ScriptCompileContext } from './script/context' export type { TypeResolveContext, + SimpleTypeResolveOptions, SimpleTypeResolveContext } from './script/resolveType' export type { diff --git a/packages/compiler-sfc/src/script/defineEmits.ts b/packages/compiler-sfc/src/script/defineEmits.ts index 8bd4cdfe543..b7453076cfe 100644 --- a/packages/compiler-sfc/src/script/defineEmits.ts +++ b/packages/compiler-sfc/src/script/defineEmits.ts @@ -8,7 +8,11 @@ import { } from '@babel/types' import { isCallOf } from './utils' import { ScriptCompileContext } from './context' -import { resolveTypeElements, resolveUnionType } from './resolveType' +import { + TypeResolveContext, + resolveTypeElements, + resolveUnionType +} from './resolveType' export const DEFINE_EMITS = 'defineEmits' @@ -64,7 +68,7 @@ export function genRuntimeEmits(ctx: ScriptCompileContext): string | undefined { return emitsDecl } -function extractRuntimeEmits(ctx: ScriptCompileContext): Set { +export function extractRuntimeEmits(ctx: TypeResolveContext): Set { const emits = new Set() const node = ctx.emitsTypeDecl! @@ -97,7 +101,7 @@ function extractRuntimeEmits(ctx: ScriptCompileContext): Set { } function extractEventNames( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, eventName: ArrayPattern | Identifier | ObjectPattern | RestElement, emits: Set ) { diff --git a/packages/compiler-sfc/src/script/defineProps.ts b/packages/compiler-sfc/src/script/defineProps.ts index 5004e314da1..449ed250d1d 100644 --- a/packages/compiler-sfc/src/script/defineProps.ts +++ b/packages/compiler-sfc/src/script/defineProps.ts @@ -8,7 +8,11 @@ import { } from '@babel/types' import { BindingTypes, isFunctionType } from '@vue/compiler-dom' import { ScriptCompileContext } from './context' -import { inferRuntimeType, resolveTypeElements } from './resolveType' +import { + TypeResolveContext, + inferRuntimeType, + resolveTypeElements +} from './resolveType' import { resolveObjectKey, UNKNOWN_TYPE, @@ -150,7 +154,7 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined { } } } else if (ctx.propsTypeDecl) { - propsDecls = genRuntimePropsFromTypes(ctx) + propsDecls = extractRuntimeProps(ctx) } const modelsDecls = genModelProps(ctx) @@ -162,7 +166,9 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined { } } -function genRuntimePropsFromTypes(ctx: ScriptCompileContext) { +export function extractRuntimeProps( + ctx: TypeResolveContext +): string | undefined { // this is only called if propsTypeDecl exists const props = resolveRuntimePropsFromType(ctx, ctx.propsTypeDecl!) if (!props.length) { @@ -175,7 +181,7 @@ function genRuntimePropsFromTypes(ctx: ScriptCompileContext) { for (const prop of props) { propStrings.push(genRuntimePropFromType(ctx, prop, hasStaticDefaults)) // register bindings - if (!(prop.key in ctx.bindingMetadata)) { + if ('bindingMetadata' in ctx && !(prop.key in ctx.bindingMetadata)) { ctx.bindingMetadata[prop.key] = BindingTypes.PROPS } } @@ -193,7 +199,7 @@ function genRuntimePropsFromTypes(ctx: ScriptCompileContext) { } function resolveRuntimePropsFromType( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, node: Node ): PropTypeData[] { const props: PropTypeData[] = [] @@ -222,7 +228,7 @@ function resolveRuntimePropsFromType( } function genRuntimePropFromType( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, { key, required, type, skipCheck }: PropTypeData, hasStaticDefaults: boolean ): string { @@ -284,7 +290,7 @@ function genRuntimePropFromType( * static properties, we can directly generate more optimized default * declarations. Otherwise we will have to fallback to runtime merging. */ -function hasStaticWithDefaults(ctx: ScriptCompileContext) { +function hasStaticWithDefaults(ctx: TypeResolveContext) { return !!( ctx.propsRuntimeDefaults && ctx.propsRuntimeDefaults.type === 'ObjectExpression' && @@ -297,7 +303,7 @@ function hasStaticWithDefaults(ctx: ScriptCompileContext) { } function genDestructuredDefaultValue( - ctx: ScriptCompileContext, + ctx: TypeResolveContext, key: string, inferredType?: string[] ): diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index 78581432366..b298f9a8e82 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -42,6 +42,13 @@ import type TS from 'typescript' import { extname, dirname } from 'path' import { minimatch as isMatch } from 'minimatch' +export type SimpleTypeResolveOptions = Partial< + Pick< + SFCScriptCompileOptions, + 'globalTypeFiles' | 'fs' | 'babelParserPlugins' | 'isProd' + > +> + /** * TypeResolveContext is compatible with ScriptCompileContext * but also allows a simpler version of it with minimal required properties @@ -59,13 +66,28 @@ import { minimatch as isMatch } from 'minimatch' */ export type SimpleTypeResolveContext = Pick< ScriptCompileContext, - // required - 'source' | 'filename' | 'error' | 'options' + // file + | 'source' + | 'filename' + + // utils + | 'error' + | 'helper' + | 'getString' + + // props + | 'propsTypeDecl' + | 'propsRuntimeDefaults' + | 'propsDestructuredBindings' + + // emits + | 'emitsTypeDecl' > & Partial< Pick > & { ast: Statement[] + options: SimpleTypeResolveOptions } export type TypeResolveContext = ScriptCompileContext | SimpleTypeResolveContext From be91d43d4915bffcd56086717fdc46ad1e026b84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8A=B1=E6=9E=9C=E5=B1=B1=E5=A4=A7=E5=9C=A3?= <316783812@qq.com> Date: Tue, 5 Sep 2023 15:55:39 +0800 Subject: [PATCH 135/192] feat(compiler-core): export error message (#8729) --- packages/compiler-core/src/index.ts | 1 + packages/compiler-dom/src/index.ts | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/compiler-core/src/index.ts b/packages/compiler-core/src/index.ts index 4898a181dfc..588bb92cc5f 100644 --- a/packages/compiler-core/src/index.ts +++ b/packages/compiler-core/src/index.ts @@ -24,6 +24,7 @@ export { export { generate, type CodegenContext, type CodegenResult } from './codegen' export { ErrorCodes, + errorMessages, createCompilerError, type CoreCompilerError, type CompilerError diff --git a/packages/compiler-dom/src/index.ts b/packages/compiler-dom/src/index.ts index 2c6f71cefbb..a2f4aff2e4c 100644 --- a/packages/compiler-dom/src/index.ts +++ b/packages/compiler-dom/src/index.ts @@ -68,5 +68,9 @@ export function parse(template: string, options: ParserOptions = {}): RootNode { export * from './runtimeHelpers' export { transformStyle } from './transforms/transformStyle' -export { createDOMCompilerError, DOMErrorCodes } from './errors' +export { + createDOMCompilerError, + DOMErrorCodes, + DOMErrorMessages +} from './errors' export * from '@vue/compiler-core' From 3a3b46dda8ea8fd883e98490fb05247f35d1a376 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 23 Sep 2023 01:35:53 +0800 Subject: [PATCH 136/192] chore: less diff --- packages/reactivity/src/effect.ts | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 410936c7a49..e1ad42029c1 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -464,21 +464,21 @@ export function triggerEffects( ) { pauseScheduling() for (const effect of dep) { - if (effect !== activeEffect || effect.allowRecurse) { - if (__DEV__ && effect.onTrigger) { - effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) - } - if (effect._dirtyLevel < dirtyLevel) { - effect._dirtyLevel = dirtyLevel - } - if ( - dirtyLevel === DirtyLevels.ComputedValueMaybeDirty || - dirtyLevel === DirtyLevels.Dirty || - (dirtyLevel === DirtyLevels.ComputedValueDirty && - !effect._queryingDirty) - ) { - effect.scheduler(pushEffectCb) - } + if (effect === activeEffect && !effect.allowRecurse) { + continue + } + if (__DEV__ && effect.onTrigger) { + effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) + } + if (effect._dirtyLevel < dirtyLevel) { + effect._dirtyLevel = dirtyLevel + } + if ( + dirtyLevel === DirtyLevels.ComputedValueMaybeDirty || + dirtyLevel === DirtyLevels.Dirty || + (dirtyLevel === DirtyLevels.ComputedValueDirty && !effect._queryingDirty) + ) { + effect.scheduler(pushEffectCb) } } resetScheduling() From 3170effc4fb9b555a5b76678671276ce5dd3dba2 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Wed, 27 Sep 2023 11:27:44 +0800 Subject: [PATCH 137/192] test: add test for #6018 --- .../__tests__/reactiveArray.spec.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/reactivity/__tests__/reactiveArray.spec.ts b/packages/reactivity/__tests__/reactiveArray.spec.ts index d749a3d97f2..f4eb7b58384 100644 --- a/packages/reactivity/__tests__/reactiveArray.spec.ts +++ b/packages/reactivity/__tests__/reactiveArray.spec.ts @@ -113,6 +113,25 @@ describe('reactivity/reactive/Array', () => { expect(fn).toHaveBeenCalledTimes(2) }) + //#6018 + test('edge case: avoid trigger effect in deleteProperty when array length-decrease mutation methods called', () => { + const arr = ref([1]) + const fn1 = vi.fn() + const fn2 = vi.fn() + effect(() => { + fn1() + if (arr.value.length > 0) { + arr.value.slice() + fn2() + } + }) + expect(fn1).toHaveBeenCalledTimes(1) + expect(fn2).toHaveBeenCalledTimes(1) + arr.value.splice(0) + expect(fn1).toHaveBeenCalledTimes(2) + expect(fn2).toHaveBeenCalledTimes(1) + }) + test('add existing index on Array should not trigger length dependency', () => { const array = new Array(3) const observed = reactive(array) From afd7c54f9f3083c0abf9d14654eb5fb76dde17a5 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 28 Sep 2023 11:08:33 +0800 Subject: [PATCH 138/192] fix: chained computed dirty reallocation after querying dirty --- .../reactivity/__tests__/computed.spec.ts | 27 +++++++++++++++++++ .../__tests__/deferredComputed.spec.ts | 3 +-- packages/reactivity/src/computed.ts | 10 +++++-- packages/reactivity/src/ref.ts | 4 +-- 4 files changed, 38 insertions(+), 6 deletions(-) diff --git a/packages/reactivity/__tests__/computed.spec.ts b/packages/reactivity/__tests__/computed.spec.ts index d68e4397da2..0aa4b183b4e 100644 --- a/packages/reactivity/__tests__/computed.spec.ts +++ b/packages/reactivity/__tests__/computed.spec.ts @@ -317,6 +317,33 @@ describe('reactivity/computed', () => { expect(cSpy).toHaveBeenCalledTimes(1) }) + // https://github.com/vuejs/core/pull/5912#issuecomment-1738257692 + it('chained computed dirty reallocation after querying dirty', () => { + let _msg: string | undefined + + const items = ref() + const isLoaded = computed(() => { + return !!items.value + }) + const msg = computed(() => { + if (isLoaded.value) { + return 'The items are loaded' + } else { + return 'The items are not loaded' + } + }) + + effect(() => { + _msg = msg.value + }) + + items.value = [1, 2, 3] + items.value = [1, 2, 3] + items.value = undefined + + expect(_msg).toBe('The items are not loaded') + }) + it('should trigger the second effect', () => { const fnSpy = vi.fn() const v = ref(1) diff --git a/packages/reactivity/__tests__/deferredComputed.spec.ts b/packages/reactivity/__tests__/deferredComputed.spec.ts index a4083b8de0a..6a75daa98e6 100644 --- a/packages/reactivity/__tests__/deferredComputed.spec.ts +++ b/packages/reactivity/__tests__/deferredComputed.spec.ts @@ -71,8 +71,7 @@ describe('deferred computed', () => { src.value = 2 src.value = 4 src.value = 6 - // c1 should re-compute once. - expect(c1Spy).toHaveBeenCalledTimes(2) + expect(c1Spy).toHaveBeenCalledTimes(4) // c2 should not have to re-compute because c1 did not change. expect(c2Spy).toHaveBeenCalledTimes(1) // effect should not trigger because c2 did not change. diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 0467f7a1564..501f9761f30 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -53,6 +53,13 @@ export class ComputedRefImpl { this[ReactiveFlags.IS_READONLY] = isReadonly } + queryDirty() { + if (this._scheduled) { + this._scheduled = false + return this.value + } + } + get value() { // the computed ref may get wrapped by other proxies e.g. readonly() #3376 const self = toRaw(this) @@ -61,7 +68,6 @@ export class ComputedRefImpl { if (hasChanged(self._value, (self._value = self.effect.run()!))) { triggerRefValue(self, DirtyLevels.ComputedValueDirty) } - self._scheduled = false } return self._value } @@ -70,7 +76,7 @@ export class ComputedRefImpl { this._setter(newValue) } - // #region polyfill _dirty for backward compatibility with version <= 3.3.x + // #region polyfill _dirty for backward compatibility third party code for Vue <= 3.3.x get _dirty() { return this.effect.dirty } diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 29f4de40087..9e379ca2e41 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -47,7 +47,7 @@ export function trackRefValue(ref: RefBase) { (ref.dep = createDep( undefined, ref instanceof ComputedRefImpl - ? () => (ref as ComputedRefImpl)._scheduled && ref.value + ? ref.queryDirty.bind(ref) : undefined )), { @@ -62,7 +62,7 @@ export function trackRefValue(ref: RefBase) { (ref.dep = createDep( undefined, ref instanceof ComputedRefImpl - ? () => (ref as ComputedRefImpl)._scheduled && ref.value + ? ref.queryDirty.bind(ref) : undefined )) ) From 0f99a017756c5eeec4cd5b337a304dd4e532fcb7 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Fri, 29 Sep 2023 10:22:37 +0800 Subject: [PATCH 139/192] fix: always cleanup effect --- .../reactivity/__tests__/computed.spec.ts | 43 +++++++++++++++++++ packages/reactivity/src/dep.ts | 8 ---- packages/reactivity/src/effect.ts | 7 +-- 3 files changed, 44 insertions(+), 14 deletions(-) diff --git a/packages/reactivity/__tests__/computed.spec.ts b/packages/reactivity/__tests__/computed.spec.ts index 0aa4b183b4e..7b6a71fd3f4 100644 --- a/packages/reactivity/__tests__/computed.spec.ts +++ b/packages/reactivity/__tests__/computed.spec.ts @@ -344,6 +344,49 @@ describe('reactivity/computed', () => { expect(_msg).toBe('The items are not loaded') }) + // https://github.com/vuejs/core/pull/5912#issuecomment-1739159832 + it('deps order should be consistent with the last time get value', () => { + const cSpy = vi.fn() + + const a = ref(0) + const b = computed(() => { + return a.value % 3 !== 0 + }) + const c = computed(() => { + cSpy() + if (a.value % 3 === 2) { + return 'expensive' + } + return 'cheap' + }) + const d = computed(() => { + return a.value % 3 === 2 + }) + const e = computed(() => { + if (b.value) { + if (d.value) { + return 'Avoiding expensive calculation' + } + } + return c.value + }) + + e.value + a.value++ + e.value + + expect(e.effect.deps.length).toBe(3) + expect(e.effect.deps.indexOf((b as any).dep)).toBe(0) + expect(e.effect.deps.indexOf((d as any).dep)).toBe(1) + expect(e.effect.deps.indexOf((c as any).dep)).toBe(2) + expect(cSpy).toHaveBeenCalledTimes(2) + + a.value++ + e.value + + expect(cSpy).toHaveBeenCalledTimes(2) + }) + it('should trigger the second effect', () => { const fnSpy = vi.fn() const v = ref(1) diff --git a/packages/reactivity/src/dep.ts b/packages/reactivity/src/dep.ts index 90857a90f1a..2108b641838 100644 --- a/packages/reactivity/src/dep.ts +++ b/packages/reactivity/src/dep.ts @@ -34,14 +34,6 @@ export const wasTracked = (dep: Dep): boolean => (dep.w & trackOpBit) > 0 export const newTracked = (dep: Dep): boolean => (dep.n & trackOpBit) > 0 -export const initDepMarkers = ({ deps }: ReactiveEffect) => { - if (deps.length) { - for (let i = 0; i < deps.length; i++) { - deps[i].w |= trackOpBit // set was tracked - } - } -} - export const finalizeDepMarkers = (effect: ReactiveEffect) => { const { deps } = effect if (deps.length) { diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index e1ad42029c1..b1ac0978b2b 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -5,7 +5,6 @@ import { createDep, Dep, finalizeDepMarkers, - initDepMarkers, newTracked, wasTracked } from './dep' @@ -138,11 +137,7 @@ export class ReactiveEffect { trackOpBit = 1 << ++effectTrackDepth - if (effectTrackDepth <= maxMarkerBits) { - initDepMarkers(this) - } else { - cleanupEffect(this) - } + cleanupEffect(this) return this.fn() } finally { if (effectTrackDepth <= maxMarkerBits) { From 34f0d26f8b70cd9d0ff18e1f33c199431f8277a5 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 30 Sep 2023 23:39:12 +0800 Subject: [PATCH 140/192] refactor: alternative for `wasTracked`, `newTracked` markers --- packages/reactivity/__tests__/effect.spec.ts | 21 --- packages/reactivity/src/dep.ts | 51 +----- packages/reactivity/src/effect.ts | 155 +++++++------------ packages/reactivity/src/ref.ts | 10 +- 4 files changed, 64 insertions(+), 173 deletions(-) diff --git a/packages/reactivity/__tests__/effect.spec.ts b/packages/reactivity/__tests__/effect.spec.ts index a78732eed2d..f6edc514d81 100644 --- a/packages/reactivity/__tests__/effect.spec.ts +++ b/packages/reactivity/__tests__/effect.spec.ts @@ -1,5 +1,4 @@ import { - ref, reactive, effect, stop, @@ -805,26 +804,6 @@ describe('reactivity/effect', () => { expect(dummy).toBe(3) }) - // #5707 - // when an effect completes its run, it should clear the tracking bits of - // its tracked deps. However, if the effect stops itself, the deps list is - // emptied so their bits are never cleared. - it('edge case: self-stopping effect tracking ref', () => { - const c = ref(true) - const runner = effect(() => { - // reference ref - if (!c.value) { - // stop itself while running - stop(runner) - } - }) - // trigger run - c.value = !c.value - // should clear bits - expect((c as any).dep.w).toBe(0) - expect((c as any).dep.n).toBe(0) - }) - it('events: onStop', () => { const onStop = vi.fn() const runner = effect(() => {}, { diff --git a/packages/reactivity/src/dep.ts b/packages/reactivity/src/dep.ts index 2108b641838..d1cac8de220 100644 --- a/packages/reactivity/src/dep.ts +++ b/packages/reactivity/src/dep.ts @@ -1,54 +1,11 @@ -import { ReactiveEffect, trackOpBit } from './effect' +import { ReactiveEffect } from './effect' -export type Dep = Set & TrackedMarkers - -/** - * wasTracked and newTracked maintain the status for several levels of effect - * tracking recursion. One bit per level is used to define whether the dependency - * was/is tracked. - */ -type TrackedMarkers = { - /** - * wasTracked - */ - w: number - /** - * newTracked - */ - n: number +export type Dep = Map & { queryDirty?: () => void } -export const createDep = ( - effects?: ReactiveEffect[], - queryDirty?: () => void -): Dep => { - const dep = new Set(effects) as Dep - dep.w = 0 - dep.n = 0 +export const createDep = (queryDirty?: () => void): Dep => { + const dep: Dep = new Map() dep.queryDirty = queryDirty return dep } - -export const wasTracked = (dep: Dep): boolean => (dep.w & trackOpBit) > 0 - -export const newTracked = (dep: Dep): boolean => (dep.n & trackOpBit) > 0 - -export const finalizeDepMarkers = (effect: ReactiveEffect) => { - const { deps } = effect - if (deps.length) { - let ptr = 0 - for (let i = 0; i < deps.length; i++) { - const dep = deps[i] - if (wasTracked(dep) && !newTracked(dep)) { - dep.delete(effect) - } else { - deps[ptr++] = dep - } - // clear bits - dep.w &= ~trackOpBit - dep.n &= ~trackOpBit - } - deps.length = ptr - } -} diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index b1ac0978b2b..2a087ce44db 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -1,13 +1,7 @@ import { DirtyLevels, TrackOpTypes, TriggerOpTypes } from './constants' import { extend, isArray, isIntegerKey, isMap } from '@vue/shared' import { EffectScope, recordEffectScope } from './effectScope' -import { - createDep, - Dep, - finalizeDepMarkers, - newTracked, - wasTracked -} from './dep' +import { createDep, Dep } from './dep' import type { ComputedRefImpl } from './computed' // The main WeakMap that stores {target -> key -> dep} connections. @@ -17,18 +11,6 @@ import type { ComputedRefImpl } from './computed' type KeyToDepMap = Map const targetMap = new WeakMap() -// The number of effects currently being tracked recursively. -let effectTrackDepth = 0 - -export let trackOpBit = 1 - -/** - * The bitwise track markers support at most 30 levels of recursion. - * This value is chosen to enable modern JS engines to use a SMI on all platforms. - * When recursion depth is greater, fall back to using a full cleanup. - */ -const maxMarkerBits = 30 - export type EffectScheduler = ( onScheduled: (cb: () => void) => void, ...args: any[] @@ -55,7 +37,6 @@ export const MAP_KEY_ITERATE_KEY = Symbol(__DEV__ ? 'Map key iterate' : '') export class ReactiveEffect { active = true deps: Dep[] = [] - parent: ReactiveEffect | undefined = undefined /** * Can be attached after creation @@ -66,10 +47,6 @@ export class ReactiveEffect { * @internal */ allowRecurse?: boolean - /** - * @internal - */ - private deferStop?: boolean onStop?: () => void // dev only @@ -79,6 +56,8 @@ export class ReactiveEffect { _dirtyLevel = DirtyLevels.Dirty _queryingDirty = false + _trackId = 0 + _runnings = 0 constructor( public fn: () => T, @@ -122,62 +101,33 @@ export class ReactiveEffect { if (!this.active) { return this.fn() } - let parent: ReactiveEffect | undefined = activeEffect let lastShouldTrack = shouldTrack - while (parent) { - if (parent === this) { - return - } - parent = parent.parent - } + let lastEffect = activeEffect try { - this.parent = activeEffect - activeEffect = this shouldTrack = true - - trackOpBit = 1 << ++effectTrackDepth - + activeEffect = this + this._runnings++ cleanupEffect(this) return this.fn() } finally { - if (effectTrackDepth <= maxMarkerBits) { - finalizeDepMarkers(this) - } - - trackOpBit = 1 << --effectTrackDepth - - activeEffect = this.parent + this._runnings-- + activeEffect = lastEffect shouldTrack = lastShouldTrack - this.parent = undefined - - if (this.deferStop) { - this.stop() - } } } stop() { - // stopped while running itself - defer the cleanup - if (activeEffect === this) { - this.deferStop = true - } else if (this.active) { + if (this.active) { cleanupEffect(this) - if (this.onStop) { - this.onStop() - } + this.onStop?.() this.active = false } } } function cleanupEffect(effect: ReactiveEffect) { - const { deps } = effect - if (deps.length) { - for (let i = 0; i < deps.length; i++) { - deps[i].delete(effect) - } - deps.length = 0 - } + effect._trackId++ + effect.deps.length = 0 } export interface DebuggerOptions { @@ -313,42 +263,28 @@ export function track(target: object, type: TrackOpTypes, key: unknown) { if (!dep) { depsMap.set(key, (dep = createDep())) } - - const eventInfo = __DEV__ - ? { effect: activeEffect, target, type, key } - : undefined - - trackEffects(dep, eventInfo) + if (__DEV__) { + trackEffect(activeEffect, dep, { + target, + type, + key + }) + } else { + trackEffect(activeEffect, dep) + } } } -export function trackEffects( +export function trackEffect( + effect: ReactiveEffect, dep: Dep, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { - let shouldTrack = false - if (effectTrackDepth <= maxMarkerBits) { - if (!newTracked(dep)) { - dep.n |= trackOpBit // set newly tracked - shouldTrack = !wasTracked(dep) - } - } else { - // Full cleanup mode. - shouldTrack = !dep.has(activeEffect!) - } - - if (shouldTrack) { - dep.add(activeEffect!) - activeEffect!.deps.push(dep) - if (__DEV__ && activeEffect!.onTrack) { - activeEffect!.onTrack( - extend( - { - effect: activeEffect! - }, - debuggerEventExtraInfo! - ) - ) + if (dep.get(effect) !== effect._trackId) { + dep.set(effect, effect._trackId) + effect.deps.push(dep) + if (__DEV__) { + effect.onTrack?.(extend({ effect }, debuggerEventExtraInfo!)) } } } @@ -435,16 +371,20 @@ export function trigger( } } } else { - const effects: ReactiveEffect[] = [] + const newDep = createDep() for (const dep of deps) { if (dep) { - effects.push(...dep) + for (const [effect, trackId] of dep) { + if (effect._trackId === trackId) { + newDep.set(effect, effect._trackId) + } + } } } if (__DEV__) { - triggerEffects(createDep(effects), DirtyLevels.Dirty, eventInfo) + triggerEffects(newDep, DirtyLevels.Dirty, eventInfo) } else { - triggerEffects(createDep(effects), DirtyLevels.Dirty) + triggerEffects(newDep, DirtyLevels.Dirty) } } } @@ -457,13 +397,20 @@ export function triggerEffects( dirtyLevel: DirtyLevels, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { + let invalidEffects: ReactiveEffect[] | undefined + pauseScheduling() - for (const effect of dep) { - if (effect === activeEffect && !effect.allowRecurse) { + for (const [effect, trackId] of dep) { + if (effect._trackId !== trackId) { + invalidEffects ??= [] + invalidEffects.push(effect) + continue + } + if (!effect.allowRecurse && effect._runnings) { continue } - if (__DEV__ && effect.onTrigger) { - effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) + if (__DEV__) { + effect.onTrigger?.(extend({ effect }, debuggerEventExtraInfo)) } if (effect._dirtyLevel < dirtyLevel) { effect._dirtyLevel = dirtyLevel @@ -477,6 +424,14 @@ export function triggerEffects( } } resetScheduling() + + if (invalidEffects) { + for (const effect of invalidEffects) { + if (effect._trackId !== dep.get(effect)) { + dep.delete(effect) + } + } + } } export function getDepFromReactive(object: any, key: string | number | symbol) { diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 9e379ca2e41..904cad69ebe 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -2,7 +2,7 @@ import { activeEffect, getDepFromReactive, shouldTrack, - trackEffects, + trackEffect, triggerEffects } from './effect' import { DirtyLevels, TrackOpTypes, TriggerOpTypes } from './constants' @@ -42,10 +42,10 @@ export function trackRefValue(ref: RefBase) { if (shouldTrack && activeEffect) { ref = toRaw(ref) if (__DEV__) { - trackEffects( + trackEffect( + activeEffect, ref.dep || (ref.dep = createDep( - undefined, ref instanceof ComputedRefImpl ? ref.queryDirty.bind(ref) : undefined @@ -57,10 +57,10 @@ export function trackRefValue(ref: RefBase) { } ) } else { - trackEffects( + trackEffect( + activeEffect, ref.dep || (ref.dep = createDep( - undefined, ref instanceof ComputedRefImpl ? ref.queryDirty.bind(ref) : undefined From de25d13225ecf37ce226fa58b139f2671e374303 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 1 Oct 2023 01:49:23 +0800 Subject: [PATCH 141/192] perf: don't set deps length to 0 if no change --- packages/reactivity/src/effect.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 2a087ce44db..7ac22486bfd 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -58,6 +58,7 @@ export class ReactiveEffect { _queryingDirty = false _trackId = 0 _runnings = 0 + _depsWriteIndex = 0 constructor( public fn: () => T, @@ -107,9 +108,13 @@ export class ReactiveEffect { shouldTrack = true activeEffect = this this._runnings++ - cleanupEffect(this) + this._trackId++ + this._depsWriteIndex = 0 return this.fn() } finally { + if (this.deps.length > this._depsWriteIndex) { + this.deps.length = this._depsWriteIndex + } this._runnings-- activeEffect = lastEffect shouldTrack = lastShouldTrack @@ -118,18 +123,15 @@ export class ReactiveEffect { stop() { if (this.active) { - cleanupEffect(this) + this._trackId++ + this._depsWriteIndex = 0 + this.deps.length = 0 this.onStop?.() this.active = false } } } -function cleanupEffect(effect: ReactiveEffect) { - effect._trackId++ - effect.deps.length = 0 -} - export interface DebuggerOptions { onTrack?: (event: DebuggerEvent) => void onTrigger?: (event: DebuggerEvent) => void @@ -282,7 +284,7 @@ export function trackEffect( ) { if (dep.get(effect) !== effect._trackId) { dep.set(effect, effect._trackId) - effect.deps.push(dep) + effect.deps[effect._depsWriteIndex++] = dep if (__DEV__) { effect.onTrack?.(extend({ effect }, debuggerEventExtraInfo!)) } From a9f0c02ece55e0bc22299bbbe46cb8c93fb28dcf Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 1 Oct 2023 02:12:25 +0800 Subject: [PATCH 142/192] chore: cleanup invalid deps for `trigger()` --- packages/reactivity/src/effect.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 7ac22486bfd..bbd666aa869 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -376,9 +376,22 @@ export function trigger( const newDep = createDep() for (const dep of deps) { if (dep) { + let invalidEffects: ReactiveEffect[] | undefined + for (const [effect, trackId] of dep) { if (effect._trackId === trackId) { newDep.set(effect, effect._trackId) + } else { + invalidEffects ??= [] + invalidEffects.push(effect) + } + } + + if (invalidEffects) { + for (const effect of invalidEffects) { + if (effect._trackId !== dep.get(effect)) { + dep.delete(effect) + } } } } From 6cd834045856d320eb2f89e9129295bbc26a32a5 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 1 Oct 2023 02:31:52 +0800 Subject: [PATCH 143/192] chore: remove unneeded code --- packages/reactivity/src/effect.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index bbd666aa869..d4191281492 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -389,9 +389,7 @@ export function trigger( if (invalidEffects) { for (const effect of invalidEffects) { - if (effect._trackId !== dep.get(effect)) { - dep.delete(effect) - } + dep.delete(effect) } } } @@ -442,9 +440,7 @@ export function triggerEffects( if (invalidEffects) { for (const effect of invalidEffects) { - if (effect._trackId !== dep.get(effect)) { - dep.delete(effect) - } + dep.delete(effect) } } } From 6e8700e18ca8d60c930480991dd2d529b02195f2 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Mon, 2 Oct 2023 23:43:33 +0800 Subject: [PATCH 144/192] refactor: simplify `trigger()` --- packages/reactivity/src/effect.ts | 49 ++++++++++--------------------- 1 file changed, 15 insertions(+), 34 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index d4191281492..990b9f0da0c 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -53,6 +53,8 @@ export class ReactiveEffect { onTrack?: (event: DebuggerEvent) => void // dev only onTrigger?: (event: DebuggerEvent) => void + // dev only + debuggerEventExtraInfo?: DebuggerEventExtraInfo _dirtyLevel = DirtyLevels.Dirty _queryingDirty = false @@ -90,6 +92,10 @@ export class ReactiveEffect { } run() { + if (__DEV__ && this.debuggerEventExtraInfo) { + this.onTrigger?.(extend({ effect: this }, this.debuggerEventExtraInfo)) + this.debuggerEventExtraInfo = undefined + } this._dirtyLevel = DirtyLevels.NotDirty const result = this._run() if ((this._dirtyLevel as DirtyLevels) === DirtyLevels.ComputedValueDirty) { @@ -364,42 +370,17 @@ export function trigger( ? { target, type, key, newValue, oldValue, oldTarget } : undefined - if (deps.length === 1) { - if (deps[0]) { + pauseScheduling() + for (const dep of deps) { + if (dep) { if (__DEV__) { - triggerEffects(deps[0], DirtyLevels.Dirty, eventInfo) + triggerEffects(dep, DirtyLevels.Dirty, eventInfo) } else { - triggerEffects(deps[0], DirtyLevels.Dirty) - } - } - } else { - const newDep = createDep() - for (const dep of deps) { - if (dep) { - let invalidEffects: ReactiveEffect[] | undefined - - for (const [effect, trackId] of dep) { - if (effect._trackId === trackId) { - newDep.set(effect, effect._trackId) - } else { - invalidEffects ??= [] - invalidEffects.push(effect) - } - } - - if (invalidEffects) { - for (const effect of invalidEffects) { - dep.delete(effect) - } - } + triggerEffects(dep, DirtyLevels.Dirty) } } - if (__DEV__) { - triggerEffects(newDep, DirtyLevels.Dirty, eventInfo) - } else { - triggerEffects(newDep, DirtyLevels.Dirty) - } } + resetScheduling() } const queueEffectCbs: (() => void)[] = [] @@ -422,9 +403,6 @@ export function triggerEffects( if (!effect.allowRecurse && effect._runnings) { continue } - if (__DEV__) { - effect.onTrigger?.(extend({ effect }, debuggerEventExtraInfo)) - } if (effect._dirtyLevel < dirtyLevel) { effect._dirtyLevel = dirtyLevel } @@ -433,6 +411,9 @@ export function triggerEffects( dirtyLevel === DirtyLevels.Dirty || (dirtyLevel === DirtyLevels.ComputedValueDirty && !effect._queryingDirty) ) { + if (__DEV__) { + effect.debuggerEventExtraInfo = debuggerEventExtraInfo + } effect.scheduler(pushEffectCb) } } From c75c3b3575a5fd9e2f926347e9084383278bedd2 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Mon, 2 Oct 2023 23:44:55 +0800 Subject: [PATCH 145/192] chore: inline debug info --- packages/reactivity/src/effect.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 990b9f0da0c..3df5a8a4cb6 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -366,15 +366,18 @@ export function trigger( } } - const eventInfo = __DEV__ - ? { target, type, key, newValue, oldValue, oldTarget } - : undefined - pauseScheduling() for (const dep of deps) { if (dep) { if (__DEV__) { - triggerEffects(dep, DirtyLevels.Dirty, eventInfo) + triggerEffects(dep, DirtyLevels.Dirty, { + target, + type, + key, + newValue, + oldValue, + oldTarget + }) } else { triggerEffects(dep, DirtyLevels.Dirty) } From 191e00b80a2af01dcea1386df1c796bdd6b14843 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 3 Oct 2023 00:26:57 +0800 Subject: [PATCH 146/192] chore: redo `cleanupEffect()` --- packages/reactivity/src/effect.ts | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 3df5a8a4cb6..4c5d6f1d979 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -114,13 +114,10 @@ export class ReactiveEffect { shouldTrack = true activeEffect = this this._runnings++ - this._trackId++ - this._depsWriteIndex = 0 + cleanupEffect(this) return this.fn() } finally { - if (this.deps.length > this._depsWriteIndex) { - this.deps.length = this._depsWriteIndex - } + postCleanupEffect(this) this._runnings-- activeEffect = lastEffect shouldTrack = lastShouldTrack @@ -129,15 +126,25 @@ export class ReactiveEffect { stop() { if (this.active) { - this._trackId++ - this._depsWriteIndex = 0 - this.deps.length = 0 + cleanupEffect(this) + postCleanupEffect(this) this.onStop?.() this.active = false } } } +function cleanupEffect(effect: ReactiveEffect) { + effect._trackId++ + effect._depsWriteIndex = 0 +} + +function postCleanupEffect(effect: ReactiveEffect) { + if (effect.deps.length > effect._depsWriteIndex) { + effect.deps.length = effect._depsWriteIndex + } +} + export interface DebuggerOptions { onTrack?: (event: DebuggerEvent) => void onTrigger?: (event: DebuggerEvent) => void From 951073b152c757ab063378266cbaf6dc799f1373 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 3 Oct 2023 03:31:55 +0800 Subject: [PATCH 147/192] perf: only schedule when dirty level change --- packages/reactivity/src/effect.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 4c5d6f1d979..be7a12f90bf 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -415,16 +415,17 @@ export function triggerEffects( } if (effect._dirtyLevel < dirtyLevel) { effect._dirtyLevel = dirtyLevel - } - if ( - dirtyLevel === DirtyLevels.ComputedValueMaybeDirty || - dirtyLevel === DirtyLevels.Dirty || - (dirtyLevel === DirtyLevels.ComputedValueDirty && !effect._queryingDirty) - ) { - if (__DEV__) { - effect.debuggerEventExtraInfo = debuggerEventExtraInfo + if ( + dirtyLevel === DirtyLevels.ComputedValueMaybeDirty || + dirtyLevel === DirtyLevels.Dirty || + (dirtyLevel === DirtyLevels.ComputedValueDirty && + !effect._queryingDirty) + ) { + if (__DEV__) { + effect.debuggerEventExtraInfo = debuggerEventExtraInfo + } + effect.scheduler(pushEffectCb) } - effect.scheduler(pushEffectCb) } } resetScheduling() From 2cf67b1e643f8a86993f5574a708e86e50a22c1f Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 3 Oct 2023 03:50:56 +0800 Subject: [PATCH 148/192] perf: use number for `pauseScheduling()` --- packages/reactivity/src/effect.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index be7a12f90bf..4fc85459adc 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -210,10 +210,9 @@ export function stop(runner: ReactiveEffectRunner) { } export let shouldTrack = true -export let shouldSchedule = true +export let pauseScheduleStack = 0 const trackStack: boolean[] = [] -const scheduleStack: boolean[] = [] /** * Temporarily pauses tracking. @@ -243,17 +242,15 @@ export function resetTracking() { * @internal */ export function pauseScheduling() { - scheduleStack.push(shouldSchedule) - shouldSchedule = false + pauseScheduleStack++ } /** * @internal */ export function resetScheduling() { - const last = scheduleStack.pop() - shouldSchedule = last === undefined ? true : last - while (shouldSchedule && queueEffectCbs.length) { + pauseScheduleStack-- + while (!pauseScheduleStack && queueEffectCbs.length) { queueEffectCbs.shift()!() } } From ca1e1f1ea3a60cd123ee4797087079124d973132 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 3 Oct 2023 03:54:51 +0800 Subject: [PATCH 149/192] chore: less condition --- packages/reactivity/src/effect.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 4fc85459adc..02413a34084 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -413,10 +413,8 @@ export function triggerEffects( if (effect._dirtyLevel < dirtyLevel) { effect._dirtyLevel = dirtyLevel if ( - dirtyLevel === DirtyLevels.ComputedValueMaybeDirty || - dirtyLevel === DirtyLevels.Dirty || - (dirtyLevel === DirtyLevels.ComputedValueDirty && - !effect._queryingDirty) + !effect._queryingDirty || + dirtyLevel !== DirtyLevels.ComputedValueDirty ) { if (__DEV__) { effect.debuggerEventExtraInfo = debuggerEventExtraInfo From 16127eb1e2d0dfb341f8012bfdaff3e9d6b3a145 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 3 Oct 2023 21:34:53 +0800 Subject: [PATCH 150/192] chore: cleanup invalid effects before trigger callbacks --- packages/reactivity/src/effect.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 02413a34084..8ad3f18c4ef 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -398,9 +398,9 @@ export function triggerEffects( dirtyLevel: DirtyLevels, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { + pauseScheduling() let invalidEffects: ReactiveEffect[] | undefined - pauseScheduling() for (const [effect, trackId] of dep) { if (effect._trackId !== trackId) { invalidEffects ??= [] @@ -423,13 +423,13 @@ export function triggerEffects( } } } - resetScheduling() if (invalidEffects) { for (const effect of invalidEffects) { dep.delete(effect) } } + resetScheduling() } export function getDepFromReactive(object: any, key: string | number | symbol) { From e0b1b885cf1704e9bf70e1da75e0e650e0e24758 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 3 Oct 2023 22:34:54 +0800 Subject: [PATCH 151/192] refactor: only trigger with dirty level 0 --- packages/reactivity/src/effect.ts | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 8ad3f18c4ef..97678e701cb 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -53,8 +53,6 @@ export class ReactiveEffect { onTrack?: (event: DebuggerEvent) => void // dev only onTrigger?: (event: DebuggerEvent) => void - // dev only - debuggerEventExtraInfo?: DebuggerEventExtraInfo _dirtyLevel = DirtyLevels.Dirty _queryingDirty = false @@ -92,19 +90,7 @@ export class ReactiveEffect { } run() { - if (__DEV__ && this.debuggerEventExtraInfo) { - this.onTrigger?.(extend({ effect: this }, this.debuggerEventExtraInfo)) - this.debuggerEventExtraInfo = undefined - } this._dirtyLevel = DirtyLevels.NotDirty - const result = this._run() - if ((this._dirtyLevel as DirtyLevels) === DirtyLevels.ComputedValueDirty) { - this._dirtyLevel-- - } - return result - } - - _run() { if (!this.active) { return this.fn() } @@ -410,14 +396,19 @@ export function triggerEffects( if (!effect.allowRecurse && effect._runnings) { continue } - if (effect._dirtyLevel < dirtyLevel) { + if ( + effect._dirtyLevel < dirtyLevel && + (!effect._runnings || dirtyLevel !== DirtyLevels.ComputedValueDirty) + ) { + const lastDirtyLevel = effect._dirtyLevel effect._dirtyLevel = dirtyLevel if ( - !effect._queryingDirty || - dirtyLevel !== DirtyLevels.ComputedValueDirty + lastDirtyLevel === DirtyLevels.NotDirty && + (!effect._queryingDirty || + dirtyLevel !== DirtyLevels.ComputedValueDirty) ) { if (__DEV__) { - effect.debuggerEventExtraInfo = debuggerEventExtraInfo + effect.onTrigger?.(extend({ effect }, debuggerEventExtraInfo)) } effect.scheduler(pushEffectCb) } From 586aa70d79b6efaeed0026dbb56131892e0005f2 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 3 Oct 2023 22:50:01 +0800 Subject: [PATCH 152/192] perf: defer reset `_scheduled` --- packages/reactivity/src/computed.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 501f9761f30..64705e83bf0 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -55,8 +55,9 @@ export class ComputedRefImpl { queryDirty() { if (this._scheduled) { + const res = this.value this._scheduled = false - return this.value + return res } } From 2785f88b611632954efef8701dd5551f97371dd3 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Wed, 4 Oct 2023 23:25:48 +0800 Subject: [PATCH 153/192] fix(computed): dirty not schedule edge case --- .../reactivity/__tests__/computed.spec.ts | 25 +++++++++++++++++++ packages/reactivity/src/computed.ts | 1 + 2 files changed, 26 insertions(+) diff --git a/packages/reactivity/__tests__/computed.spec.ts b/packages/reactivity/__tests__/computed.spec.ts index 7b6a71fd3f4..9a68834a0ba 100644 --- a/packages/reactivity/__tests__/computed.spec.ts +++ b/packages/reactivity/__tests__/computed.spec.ts @@ -344,6 +344,31 @@ describe('reactivity/computed', () => { expect(_msg).toBe('The items are not loaded') }) + it('chained computed dirty reallocation after trigger computed getter', () => { + let _msg: string | undefined + + const items = ref() + const isLoaded = computed(() => { + return !!items.value + }) + const msg = computed(() => { + if (isLoaded.value) { + return 'The items are loaded' + } else { + return 'The items are not loaded' + } + }) + + _msg = msg.value + items.value = [1, 2, 3] + isLoaded.value // <- trigger computed getter + _msg = msg.value + items.value = undefined + _msg = msg.value + + expect(_msg).toBe('The items are not loaded') + }) + // https://github.com/vuejs/core/pull/5912#issuecomment-1739159832 it('deps order should be consistent with the last time get value', () => { const cSpy = vi.fn() diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 64705e83bf0..e524f35f470 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -69,6 +69,7 @@ export class ComputedRefImpl { if (hasChanged(self._value, (self._value = self.effect.run()!))) { triggerRefValue(self, DirtyLevels.ComputedValueDirty) } + self._scheduled = false } return self._value } From 33aab34119563dde12f5187a46c8dbd593e643b5 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 5 Oct 2023 00:30:18 +0800 Subject: [PATCH 154/192] chore(computed): only reset _scheduled when value change --- packages/reactivity/src/computed.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index e524f35f470..91aeeee27a5 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -68,8 +68,8 @@ export class ComputedRefImpl { if (!self._cacheable || self.effect.dirty) { if (hasChanged(self._value, (self._value = self.effect.run()!))) { triggerRefValue(self, DirtyLevels.ComputedValueDirty) + self._scheduled = false } - self._scheduled = false } return self._value } From 0636bac60d8d726c77a47cb0dc08bfd2ebb83b31 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 5 Oct 2023 01:38:26 +0800 Subject: [PATCH 155/192] test(computed): add a failed edge case --- .../reactivity/__tests__/computed.spec.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/reactivity/__tests__/computed.spec.ts b/packages/reactivity/__tests__/computed.spec.ts index 9a68834a0ba..d9b8f888caf 100644 --- a/packages/reactivity/__tests__/computed.spec.ts +++ b/packages/reactivity/__tests__/computed.spec.ts @@ -412,6 +412,28 @@ describe('reactivity/computed', () => { expect(cSpy).toHaveBeenCalledTimes(2) }) + it('should trigger by the second computed that maybe dirty', () => { + const cSpy = vi.fn() + + const src1 = ref(0) + const src2 = ref(0) + const c1 = computed(() => src1.value) + const c2 = computed(() => (src1.value % 2) + src2.value) + const c3 = computed(() => { + cSpy() + c1.value + c2.value + }) + + c3.value + src1.value = 2 + c3.value + expect(cSpy).toHaveBeenCalledTimes(2) + src2.value = 1 + c3.value + expect(cSpy).toHaveBeenCalledTimes(3) + }) + it('should trigger the second effect', () => { const fnSpy = vi.fn() const v = ref(1) From e13a42181f56c13db069f0211fce45bb4d04272f Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 5 Oct 2023 01:39:22 +0800 Subject: [PATCH 156/192] fix(computed): remove unneeded `_scheduled` and fixes failed test --- packages/reactivity/src/computed.ts | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 91aeeee27a5..2b045e261dd 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -33,7 +33,6 @@ export class ComputedRefImpl { public readonly __v_isRef = true public readonly [ReactiveFlags.IS_READONLY]: boolean = false - public _scheduled = false public _cacheable: boolean constructor( @@ -43,10 +42,7 @@ export class ComputedRefImpl { isSSR: boolean ) { this.effect = new ReactiveEffect(getter, () => { - if (!this._scheduled) { - this._scheduled = true - triggerRefValue(this, DirtyLevels.ComputedValueMaybeDirty) - } + triggerRefValue(this, DirtyLevels.ComputedValueMaybeDirty) }) this.effect.computed = this this.effect.active = this._cacheable = !isSSR @@ -54,11 +50,7 @@ export class ComputedRefImpl { } queryDirty() { - if (this._scheduled) { - const res = this.value - this._scheduled = false - return res - } + return this.value } get value() { @@ -68,7 +60,6 @@ export class ComputedRefImpl { if (!self._cacheable || self.effect.dirty) { if (hasChanged(self._value, (self._value = self.effect.run()!))) { triggerRefValue(self, DirtyLevels.ComputedValueDirty) - self._scheduled = false } } return self._value From 9d31bc97705c622865795a6d9d135b76f126bd1f Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 5 Oct 2023 21:12:51 +0800 Subject: [PATCH 157/192] refactor: cleanup deps just in time and avoid memory leak --- packages/reactivity/src/effect.ts | 43 +++++++++++++++++-------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 97678e701cb..37140b3c1e3 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -58,7 +58,7 @@ export class ReactiveEffect { _queryingDirty = false _trackId = 0 _runnings = 0 - _depsWriteIndex = 0 + _depsLength = 0 constructor( public fn: () => T, @@ -122,12 +122,22 @@ export class ReactiveEffect { function cleanupEffect(effect: ReactiveEffect) { effect._trackId++ - effect._depsWriteIndex = 0 + effect._depsLength = 0 } function postCleanupEffect(effect: ReactiveEffect) { - if (effect.deps.length > effect._depsWriteIndex) { - effect.deps.length = effect._depsWriteIndex + if (effect.deps.length > effect._depsLength) { + 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) } } @@ -280,7 +290,15 @@ export function trackEffect( ) { if (dep.get(effect) !== effect._trackId) { dep.set(effect, effect._trackId) - effect.deps[effect._depsWriteIndex++] = dep + const oldDep = effect.deps[effect._depsLength] + if (oldDep !== dep) { + if (oldDep) { + cleanupDepEffect(oldDep, effect) + } + effect.deps[effect._depsLength++] = dep + } else { + effect._depsLength++ + } if (__DEV__) { effect.onTrack?.(extend({ effect }, debuggerEventExtraInfo!)) } @@ -385,14 +403,7 @@ export function triggerEffects( debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { pauseScheduling() - let invalidEffects: ReactiveEffect[] | undefined - - for (const [effect, trackId] of dep) { - if (effect._trackId !== trackId) { - invalidEffects ??= [] - invalidEffects.push(effect) - continue - } + for (const effect of dep.keys()) { if (!effect.allowRecurse && effect._runnings) { continue } @@ -414,12 +425,6 @@ export function triggerEffects( } } } - - if (invalidEffects) { - for (const effect of invalidEffects) { - dep.delete(effect) - } - } resetScheduling() } From fa80d44b2e12186a1411e3f613f84acd4f60fb18 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 5 Oct 2023 23:02:57 +0800 Subject: [PATCH 158/192] chore: remove queryDirty --- packages/reactivity/src/computed.ts | 4 ---- packages/reactivity/src/dep.ts | 9 +++++---- packages/reactivity/src/effect.ts | 12 +++++++++--- packages/reactivity/src/ref.ts | 8 ++------ 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/packages/reactivity/src/computed.ts b/packages/reactivity/src/computed.ts index 2b045e261dd..09247360d06 100644 --- a/packages/reactivity/src/computed.ts +++ b/packages/reactivity/src/computed.ts @@ -49,10 +49,6 @@ export class ComputedRefImpl { this[ReactiveFlags.IS_READONLY] = isReadonly } - queryDirty() { - return this.value - } - get value() { // the computed ref may get wrapped by other proxies e.g. readonly() #3376 const self = toRaw(this) diff --git a/packages/reactivity/src/dep.ts b/packages/reactivity/src/dep.ts index d1cac8de220..c213d18a76e 100644 --- a/packages/reactivity/src/dep.ts +++ b/packages/reactivity/src/dep.ts @@ -1,11 +1,12 @@ -import { ReactiveEffect } from './effect' +import type { ReactiveEffect } from './effect' +import type { ComputedRefImpl } from './computed' export type Dep = Map & { - queryDirty?: () => void + computed?: ComputedRefImpl } -export const createDep = (queryDirty?: () => void): Dep => { +export const createDep = (computed?: ComputedRefImpl): Dep => { const dep: Dep = new Map() - dep.queryDirty = queryDirty + dep.computed = computed return dep } diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 37140b3c1e3..39e863955d6 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -74,9 +74,11 @@ export class ReactiveEffect { this._queryingDirty = true pauseTracking() for (const dep of this.deps) { - dep.queryDirty?.() - if (this._dirtyLevel >= DirtyLevels.ComputedValueDirty) { - break + if (dep.computed) { + triggerComputed(dep.computed) + if (this._dirtyLevel >= DirtyLevels.ComputedValueDirty) { + break + } } } resetTracking() @@ -120,6 +122,10 @@ export class ReactiveEffect { } } +function triggerComputed(computed: ComputedRefImpl) { + return computed.value +} + function cleanupEffect(effect: ReactiveEffect) { effect._trackId++ effect._depsLength = 0 diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 904cad69ebe..0929a51c235 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -46,9 +46,7 @@ export function trackRefValue(ref: RefBase) { activeEffect, ref.dep || (ref.dep = createDep( - ref instanceof ComputedRefImpl - ? ref.queryDirty.bind(ref) - : undefined + ref instanceof ComputedRefImpl ? ref : undefined )), { target: ref, @@ -61,9 +59,7 @@ export function trackRefValue(ref: RefBase) { activeEffect, ref.dep || (ref.dep = createDep( - ref instanceof ComputedRefImpl - ? ref.queryDirty.bind(ref) - : undefined + ref instanceof ComputedRefImpl ? ref : undefined )) ) } From adc3ad7dc34fb8d4ba1cd9bc7b14f7f807d07e25 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Tue, 10 Oct 2023 16:01:33 +0800 Subject: [PATCH 159/192] chore: trigger side effects after scheduled for watch() and render() --- packages/runtime-core/src/apiWatch.ts | 14 ++++++++------ packages/runtime-core/src/renderer.ts | 3 ++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 5e575ff74e5..cf87f6985db 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -6,7 +6,6 @@ import { ReactiveEffect, isReactive, ReactiveFlags, - EffectScheduler, DebuggerOptions, getCurrentScope } from '@vue/reactivity' @@ -364,19 +363,22 @@ function doWatch( // it is allowed to self-trigger (#1727) job.allowRecurse = !!cb - let scheduler: EffectScheduler + let schedulerJob: SchedulerJob if (flush === 'sync') { - scheduler = onScheduled => onScheduled(job as any) // the scheduler function gets called directly + schedulerJob = job // the scheduler function gets called directly } else if (flush === 'post') { - scheduler = () => queuePostRenderEffect(job, instance && instance.suspense) + schedulerJob = () => + queuePostRenderEffect(job, instance && instance.suspense) } else { // default: 'pre' job.pre = true if (instance) job.id = instance.uid - scheduler = () => queueJob(job) + schedulerJob = () => queueJob(job) } - const effect = new ReactiveEffect(getter, scheduler) + const effect = new ReactiveEffect(getter, onScheduled => { + onScheduled(schedulerJob as any) + }) const unwatch = () => { effect.stop() diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index eeee23b391d..e6b5c0e4639 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -1545,9 +1545,10 @@ function baseCreateRenderer( } // create reactive effect for rendering + const schedulerJob = () => queueJob(update) const effect = (instance.effect = new ReactiveEffect( componentUpdateFn, - () => queueJob(update), + onScheduled => onScheduled(schedulerJob), instance.scope // track it in component's effect scope )) From dc54c59ed57551648f65d48af12b0c93a57d860c Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 14 Oct 2023 16:31:42 +0800 Subject: [PATCH 160/192] Revert "feat(compiler-core): export error message (#8729)" This reverts commit be91d43d4915bffcd56086717fdc46ad1e026b84. --- packages/compiler-core/src/index.ts | 1 - packages/compiler-dom/src/index.ts | 6 +----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/compiler-core/src/index.ts b/packages/compiler-core/src/index.ts index 588bb92cc5f..4898a181dfc 100644 --- a/packages/compiler-core/src/index.ts +++ b/packages/compiler-core/src/index.ts @@ -24,7 +24,6 @@ export { export { generate, type CodegenContext, type CodegenResult } from './codegen' export { ErrorCodes, - errorMessages, createCompilerError, type CoreCompilerError, type CompilerError diff --git a/packages/compiler-dom/src/index.ts b/packages/compiler-dom/src/index.ts index a2f4aff2e4c..2c6f71cefbb 100644 --- a/packages/compiler-dom/src/index.ts +++ b/packages/compiler-dom/src/index.ts @@ -68,9 +68,5 @@ export function parse(template: string, options: ParserOptions = {}): RootNode { export * from './runtimeHelpers' export { transformStyle } from './transforms/transformStyle' -export { - createDOMCompilerError, - DOMErrorCodes, - DOMErrorMessages -} from './errors' +export { createDOMCompilerError, DOMErrorCodes } from './errors' export * from '@vue/compiler-core' From 59a8c26e60d9681d4755b5db9d32f08662d0af52 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 14 Oct 2023 16:31:47 +0800 Subject: [PATCH 161/192] Revert "feat(compiler-sfc): expose resolve type-based props and emits (#8874)" This reverts commit d52617fc15fcb48a47a12e0d7f57d34a2cc6bcb5. --- packages/compiler-sfc/src/index.ts | 3 --- .../compiler-sfc/src/script/defineEmits.ts | 10 +++---- .../compiler-sfc/src/script/defineProps.ts | 22 ++++++---------- .../compiler-sfc/src/script/resolveType.ts | 26 ++----------------- 4 files changed, 13 insertions(+), 48 deletions(-) diff --git a/packages/compiler-sfc/src/index.ts b/packages/compiler-sfc/src/index.ts index c6ee604146e..76b4900d46d 100644 --- a/packages/compiler-sfc/src/index.ts +++ b/packages/compiler-sfc/src/index.ts @@ -33,8 +33,6 @@ export { // Internals for type resolution export { invalidateTypeCache, registerTS } from './script/resolveType' -export { extractRuntimeProps } from './script/defineProps' -export { extractRuntimeEmits } from './script/defineEmits' // Types export type { @@ -60,7 +58,6 @@ export type { SFCScriptCompileOptions } from './compileScript' export type { ScriptCompileContext } from './script/context' export type { TypeResolveContext, - SimpleTypeResolveOptions, SimpleTypeResolveContext } from './script/resolveType' export type { diff --git a/packages/compiler-sfc/src/script/defineEmits.ts b/packages/compiler-sfc/src/script/defineEmits.ts index b7453076cfe..8bd4cdfe543 100644 --- a/packages/compiler-sfc/src/script/defineEmits.ts +++ b/packages/compiler-sfc/src/script/defineEmits.ts @@ -8,11 +8,7 @@ import { } from '@babel/types' import { isCallOf } from './utils' import { ScriptCompileContext } from './context' -import { - TypeResolveContext, - resolveTypeElements, - resolveUnionType -} from './resolveType' +import { resolveTypeElements, resolveUnionType } from './resolveType' export const DEFINE_EMITS = 'defineEmits' @@ -68,7 +64,7 @@ export function genRuntimeEmits(ctx: ScriptCompileContext): string | undefined { return emitsDecl } -export function extractRuntimeEmits(ctx: TypeResolveContext): Set { +function extractRuntimeEmits(ctx: ScriptCompileContext): Set { const emits = new Set() const node = ctx.emitsTypeDecl! @@ -101,7 +97,7 @@ export function extractRuntimeEmits(ctx: TypeResolveContext): Set { } function extractEventNames( - ctx: TypeResolveContext, + ctx: ScriptCompileContext, eventName: ArrayPattern | Identifier | ObjectPattern | RestElement, emits: Set ) { diff --git a/packages/compiler-sfc/src/script/defineProps.ts b/packages/compiler-sfc/src/script/defineProps.ts index 449ed250d1d..5004e314da1 100644 --- a/packages/compiler-sfc/src/script/defineProps.ts +++ b/packages/compiler-sfc/src/script/defineProps.ts @@ -8,11 +8,7 @@ import { } from '@babel/types' import { BindingTypes, isFunctionType } from '@vue/compiler-dom' import { ScriptCompileContext } from './context' -import { - TypeResolveContext, - inferRuntimeType, - resolveTypeElements -} from './resolveType' +import { inferRuntimeType, resolveTypeElements } from './resolveType' import { resolveObjectKey, UNKNOWN_TYPE, @@ -154,7 +150,7 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined { } } } else if (ctx.propsTypeDecl) { - propsDecls = extractRuntimeProps(ctx) + propsDecls = genRuntimePropsFromTypes(ctx) } const modelsDecls = genModelProps(ctx) @@ -166,9 +162,7 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined { } } -export function extractRuntimeProps( - ctx: TypeResolveContext -): string | undefined { +function genRuntimePropsFromTypes(ctx: ScriptCompileContext) { // this is only called if propsTypeDecl exists const props = resolveRuntimePropsFromType(ctx, ctx.propsTypeDecl!) if (!props.length) { @@ -181,7 +175,7 @@ export function extractRuntimeProps( for (const prop of props) { propStrings.push(genRuntimePropFromType(ctx, prop, hasStaticDefaults)) // register bindings - if ('bindingMetadata' in ctx && !(prop.key in ctx.bindingMetadata)) { + if (!(prop.key in ctx.bindingMetadata)) { ctx.bindingMetadata[prop.key] = BindingTypes.PROPS } } @@ -199,7 +193,7 @@ export function extractRuntimeProps( } function resolveRuntimePropsFromType( - ctx: TypeResolveContext, + ctx: ScriptCompileContext, node: Node ): PropTypeData[] { const props: PropTypeData[] = [] @@ -228,7 +222,7 @@ function resolveRuntimePropsFromType( } function genRuntimePropFromType( - ctx: TypeResolveContext, + ctx: ScriptCompileContext, { key, required, type, skipCheck }: PropTypeData, hasStaticDefaults: boolean ): string { @@ -290,7 +284,7 @@ function genRuntimePropFromType( * static properties, we can directly generate more optimized default * declarations. Otherwise we will have to fallback to runtime merging. */ -function hasStaticWithDefaults(ctx: TypeResolveContext) { +function hasStaticWithDefaults(ctx: ScriptCompileContext) { return !!( ctx.propsRuntimeDefaults && ctx.propsRuntimeDefaults.type === 'ObjectExpression' && @@ -303,7 +297,7 @@ function hasStaticWithDefaults(ctx: TypeResolveContext) { } function genDestructuredDefaultValue( - ctx: TypeResolveContext, + ctx: ScriptCompileContext, key: string, inferredType?: string[] ): diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index b298f9a8e82..78581432366 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -42,13 +42,6 @@ import type TS from 'typescript' import { extname, dirname } from 'path' import { minimatch as isMatch } from 'minimatch' -export type SimpleTypeResolveOptions = Partial< - Pick< - SFCScriptCompileOptions, - 'globalTypeFiles' | 'fs' | 'babelParserPlugins' | 'isProd' - > -> - /** * TypeResolveContext is compatible with ScriptCompileContext * but also allows a simpler version of it with minimal required properties @@ -66,28 +59,13 @@ export type SimpleTypeResolveOptions = Partial< */ export type SimpleTypeResolveContext = Pick< ScriptCompileContext, - // file - | 'source' - | 'filename' - - // utils - | 'error' - | 'helper' - | 'getString' - - // props - | 'propsTypeDecl' - | 'propsRuntimeDefaults' - | 'propsDestructuredBindings' - - // emits - | 'emitsTypeDecl' + // required + 'source' | 'filename' | 'error' | 'options' > & Partial< Pick > & { ast: Statement[] - options: SimpleTypeResolveOptions } export type TypeResolveContext = ScriptCompileContext | SimpleTypeResolveContext From 34a0de44923dc77f1c48fe30d7078374fdd6906a Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 14 Oct 2023 16:33:28 +0800 Subject: [PATCH 162/192] remove a test --- .../runtime-core/__tests__/apiWatch.spec.ts | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index 78af98b62fa..bddfc5ff541 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -1206,27 +1206,6 @@ describe('api: watch', () => { expect(countW).toBe(2) }) - test('should not trigger if computed value did not change', () => { - const src = ref(0) - const c = computed(() => src.value % 2) - const spy = vi.fn() - watchEffect( - () => { - spy(c.value) - }, - { flush: 'sync' } - ) - expect(spy).toHaveBeenCalledTimes(1) - src.value = 2 - - // should not trigger - expect(spy).toHaveBeenCalledTimes(1) - - src.value = 3 - // should trigger because latest value changes - expect(spy).toHaveBeenCalledTimes(2) - }) - const options = [ { name: 'only trigger once watch' }, { From 50418bea0dd8a8484be3819aae6dfa0ce7b34a1e Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 14 Oct 2023 16:33:33 +0800 Subject: [PATCH 163/192] Revert "feat(runtime-core): add `once` option to watch (#9034)" This reverts commit d573e50e3de3aa0e3ffea2861da9a7b4e6e28aa1. --- .../runtime-core/__tests__/apiWatch.spec.ts | 38 ------------------- packages/runtime-core/src/apiWatch.ts | 31 ++++----------- 2 files changed, 8 insertions(+), 61 deletions(-) diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index bddfc5ff541..f24ce80b9df 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -1205,42 +1205,4 @@ describe('api: watch', () => { expect(countWE).toBe(3) expect(countW).toBe(2) }) - - const options = [ - { name: 'only trigger once watch' }, - { - deep: true, - name: 'only trigger once watch with deep' - }, - { - flush: 'sync', - name: 'only trigger once watch with flush: sync' - }, - { - flush: 'pre', - name: 'only trigger once watch with flush: pre' - }, - { - immediate: true, - name: 'only trigger once watch with immediate' - } - ] as const - test.each(options)('$name', async option => { - const count = ref(0) - const cb = vi.fn() - - watch(count, cb, { once: true, ...option }) - - count.value++ - await nextTick() - - expect(count.value).toBe(1) - expect(cb).toHaveBeenCalledTimes(1) - - count.value++ - await nextTick() - - expect(count.value).toBe(2) - expect(cb).toHaveBeenCalledTimes(1) - }) }) diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index cf87f6985db..80f73ee0a20 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -74,7 +74,6 @@ export interface WatchOptionsBase extends DebuggerOptions { export interface WatchOptions extends WatchOptionsBase { immediate?: Immediate deep?: boolean - once?: boolean } export type WatchStopHandle = () => void @@ -172,16 +171,8 @@ export function watch = false>( function doWatch( source: WatchSource | WatchSource[] | WatchEffect | object, cb: WatchCallback | null, - { immediate, deep, flush, once, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ + { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ ): WatchStopHandle { - if (cb && once) { - const _cb = cb - cb = (...args) => { - _cb(...args) - unwatch() - } - } - if (__DEV__ && !cb) { if (immediate !== undefined) { warn( @@ -195,12 +186,6 @@ function doWatch( `watch(source, callback, options?) signature.` ) } - if (once !== undefined) { - warn( - `watch() "once" option is only respected when using the ` + - `watch(source, callback, options?) signature.` - ) - } } const warnInvalidSource = (s: unknown) => { @@ -380,13 +365,6 @@ function doWatch( onScheduled(schedulerJob as any) }) - const unwatch = () => { - effect.stop() - if (instance && instance.scope) { - remove(instance.scope.effects!, effect) - } - } - if (__DEV__) { effect.onTrack = onTrack effect.onTrigger = onTrigger @@ -408,6 +386,13 @@ function doWatch( effect.run() } + const unwatch = () => { + effect.stop() + if (instance && instance.scope) { + remove(instance.scope.effects!, effect) + } + } + if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch) return unwatch } From 93b5bebc0730a2d868d752bc77c712503b69cd38 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 19 Oct 2023 03:23:56 +0800 Subject: [PATCH 164/192] fix(reactivity): fix dep memory leak Co-Authored-By: skirtle <65301168+skirtles-code@users.noreply.github.com> #7827 --- packages/reactivity/__tests__/effect.spec.ts | 71 +++++++++++++++++++- packages/reactivity/src/dep.ts | 9 ++- packages/reactivity/src/effect.ts | 5 +- packages/reactivity/src/ref.ts | 2 + 4 files changed, 83 insertions(+), 4 deletions(-) diff --git a/packages/reactivity/__tests__/effect.spec.ts b/packages/reactivity/__tests__/effect.spec.ts index f6edc514d81..7422b4000ab 100644 --- a/packages/reactivity/__tests__/effect.spec.ts +++ b/packages/reactivity/__tests__/effect.spec.ts @@ -11,7 +11,12 @@ import { readonly, ReactiveEffectRunner } from '../src/index' -import { ITERATE_KEY, pauseScheduling, resetScheduling } from '../src/effect' +import { + ITERATE_KEY, + getDepFromReactive, + pauseScheduling, + resetScheduling +} from '../src/effect' describe('reactivity/effect', () => { it('should run the passed function once (wrapped by a effect)', () => { @@ -993,4 +998,68 @@ describe('reactivity/effect', () => { resetScheduling() expect(counterSpy).toHaveBeenCalledTimes(1) }) + + describe('empty dep cleanup', () => { + it('should remove the dep when the effect is stopped', () => { + const obj = reactive({ prop: 1 }) + expect(getDepFromReactive(toRaw(obj), 'prop')).toBeUndefined() + const runner = effect(() => obj.prop) + const dep = getDepFromReactive(toRaw(obj), 'prop') + expect(dep).toHaveLength(1) + obj.prop = 2 + expect(getDepFromReactive(toRaw(obj), 'prop')).toBe(dep) + expect(dep).toHaveLength(1) + stop(runner) + expect(getDepFromReactive(toRaw(obj), 'prop')).toBeUndefined() + obj.prop = 3 + runner() + expect(getDepFromReactive(toRaw(obj), 'prop')).toBeUndefined() + }) + + it('should only remove the dep when the last effect is stopped', () => { + const obj = reactive({ prop: 1 }) + expect(getDepFromReactive(toRaw(obj), 'prop')).toBeUndefined() + const runner1 = effect(() => obj.prop) + const dep = getDepFromReactive(toRaw(obj), 'prop') + expect(dep).toHaveLength(1) + const runner2 = effect(() => obj.prop) + expect(getDepFromReactive(toRaw(obj), 'prop')).toBe(dep) + expect(dep).toHaveLength(2) + obj.prop = 2 + expect(getDepFromReactive(toRaw(obj), 'prop')).toBe(dep) + expect(dep).toHaveLength(2) + stop(runner1) + expect(getDepFromReactive(toRaw(obj), 'prop')).toBe(dep) + expect(dep).toHaveLength(1) + obj.prop = 3 + expect(getDepFromReactive(toRaw(obj), 'prop')).toBe(dep) + expect(dep).toHaveLength(1) + stop(runner2) + expect(getDepFromReactive(toRaw(obj), 'prop')).toBeUndefined() + obj.prop = 4 + runner1() + runner2() + expect(getDepFromReactive(toRaw(obj), 'prop')).toBeUndefined() + }) + + it('should remove the dep when it is no longer used by the effect', () => { + const obj = reactive<{ a: number; b: number; c: 'a' | 'b' }>({ + a: 1, + b: 2, + c: 'a' + }) + expect(getDepFromReactive(toRaw(obj), 'prop')).toBeUndefined() + effect(() => obj[obj.c]) + const depC = getDepFromReactive(toRaw(obj), 'c') + expect(getDepFromReactive(toRaw(obj), 'a')).toHaveLength(1) + expect(getDepFromReactive(toRaw(obj), 'b')).toBeUndefined() + expect(depC).toHaveLength(1) + obj.c = 'b' + obj.a = 4 + expect(getDepFromReactive(toRaw(obj), 'a')).toBeUndefined() + expect(getDepFromReactive(toRaw(obj), 'b')).toHaveLength(1) + expect(getDepFromReactive(toRaw(obj), 'c')).toBe(depC) + expect(depC).toHaveLength(1) + }) + }) }) diff --git a/packages/reactivity/src/dep.ts b/packages/reactivity/src/dep.ts index c213d18a76e..db6dcc188bf 100644 --- a/packages/reactivity/src/dep.ts +++ b/packages/reactivity/src/dep.ts @@ -2,11 +2,16 @@ import type { ReactiveEffect } from './effect' import type { ComputedRefImpl } from './computed' export type Dep = Map & { + cleanup: () => void computed?: ComputedRefImpl } -export const createDep = (computed?: ComputedRefImpl): Dep => { - const dep: Dep = new Map() +export const createDep = ( + cleanup: () => void, + computed?: ComputedRefImpl +): Dep => { + const dep: Dep = new Map() as Dep + dep.cleanup = cleanup dep.computed = computed return dep } diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 39e863955d6..3140c4be251 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -144,6 +144,9 @@ 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() + } } } @@ -275,7 +278,7 @@ export function track(target: object, type: TrackOpTypes, key: unknown) { } let dep = depsMap.get(key) if (!dep) { - depsMap.set(key, (dep = createDep())) + depsMap.set(key, (dep = createDep(() => depsMap!.delete(key)))) } if (__DEV__) { trackEffect(activeEffect, dep, { diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 0929a51c235..d0a2fdf8e78 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -46,6 +46,7 @@ export function trackRefValue(ref: RefBase) { activeEffect, ref.dep || (ref.dep = createDep( + () => (ref.dep = undefined), ref instanceof ComputedRefImpl ? ref : undefined )), { @@ -59,6 +60,7 @@ export function trackRefValue(ref: RefBase) { activeEffect, ref.dep || (ref.dep = createDep( + () => (ref.dep = undefined), ref instanceof ComputedRefImpl ? ref : undefined )) ) From fc76e819feece41c25b5ffe9c4c9ce38ee8062e2 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 19 Oct 2023 04:04:44 +0800 Subject: [PATCH 165/192] refactor: move code to reactiveEffect.ts --- packages/reactivity/__tests__/effect.spec.ts | 8 +- packages/reactivity/src/baseHandlers.ts | 4 +- packages/reactivity/src/collectionHandlers.ts | 7 +- packages/reactivity/src/effect.ts | 141 +---------------- packages/reactivity/src/index.ts | 4 +- packages/reactivity/src/reactiveEffect.ts | 146 ++++++++++++++++++ packages/reactivity/src/ref.ts | 2 +- 7 files changed, 160 insertions(+), 152 deletions(-) create mode 100644 packages/reactivity/src/reactiveEffect.ts 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 From 66da31ab946b84e61d6fd9ecec1fc36779d47e1e Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Fri, 20 Oct 2023 16:26:48 +0800 Subject: [PATCH 166/192] fix: use `WeakRef` for dep to avoid `ReactiveEffect` memory leak (#9233) --- packages/reactivity/src/dep.ts | 4 ++-- packages/reactivity/src/effect.ts | 23 ++++++++++++++++++----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/packages/reactivity/src/dep.ts b/packages/reactivity/src/dep.ts index db6dcc188bf..cb9f7494c5f 100644 --- a/packages/reactivity/src/dep.ts +++ b/packages/reactivity/src/dep.ts @@ -1,7 +1,7 @@ import type { ReactiveEffect } from './effect' import type { ComputedRefImpl } from './computed' -export type Dep = Map & { +export type Dep = Map, number> & { cleanup: () => void computed?: ComputedRefImpl } @@ -10,7 +10,7 @@ export const createDep = ( cleanup: () => void, computed?: ComputedRefImpl ): Dep => { - const dep: Dep = new Map() as Dep + const dep = new Map() as Dep dep.cleanup = cleanup dep.computed = computed return dep diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index e3d762f2005..be1a3a251b7 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -46,6 +46,7 @@ export class ReactiveEffect { _dirtyLevel = DirtyLevels.Dirty _queryingDirty = false + _trackToken = new WeakRef(this) _trackId = 0 _runnings = 0 _depsLength = 0 @@ -131,9 +132,9 @@ function postCleanupEffect(effect: ReactiveEffect) { } function cleanupDepEffect(dep: Dep, effect: ReactiveEffect) { - const trackId = dep.get(effect) + const trackId = dep.get(effect._trackToken) if (trackId !== undefined && effect._trackId !== trackId) { - dep.delete(effect) + dep.delete(effect._trackToken) if (dep.size === 0) { dep.cleanup() } @@ -255,8 +256,8 @@ export function trackEffect( dep: Dep, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { - if (dep.get(effect) !== effect._trackId) { - dep.set(effect, effect._trackId) + if (dep.get(effect._trackToken) !== effect._trackId) { + dep.set(effect._trackToken, effect._trackId) const oldDep = effect.deps[effect._depsLength] if (oldDep !== dep) { if (oldDep) { @@ -281,7 +282,14 @@ export function triggerEffects( debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { pauseScheduling() - for (const effect of dep.keys()) { + let reclaimedTokens: WeakRef[] | undefined + for (const trackToken of dep.keys()) { + const effect = trackToken.deref() + if (!effect) { + reclaimedTokens ??= [] + reclaimedTokens.push(trackToken) + continue + } if (!effect.allowRecurse && effect._runnings) { continue } @@ -303,5 +311,10 @@ export function triggerEffects( } } } + if (reclaimedTokens) { + for (const token of reclaimedTokens) { + dep.delete(token) + } + } resetScheduling() } From 9f8f02e59366361711fbe0fc26383975cbea3ddc Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Fri, 20 Oct 2023 16:47:47 +0800 Subject: [PATCH 167/192] perf: deferred create track token --- packages/reactivity/src/effect.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index be1a3a251b7..4edcff31956 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -46,7 +46,7 @@ export class ReactiveEffect { _dirtyLevel = DirtyLevels.Dirty _queryingDirty = false - _trackToken = new WeakRef(this) + _trackToken!: WeakRef _trackId = 0 _runnings = 0 _depsLength = 0 @@ -87,6 +87,9 @@ export class ReactiveEffect { if (!this.active) { return this.fn() } + if (!this._trackToken) { + this._trackToken = new WeakRef(this) + } let lastShouldTrack = shouldTrack let lastEffect = activeEffect try { From e67116ccdb13351fbe097c025111736e6cb55a95 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Fri, 20 Oct 2023 16:48:59 +0800 Subject: [PATCH 168/192] chore: _queryingDirty -> _queryings --- packages/reactivity/src/effect.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 4edcff31956..7163a7100d3 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -45,10 +45,10 @@ export class ReactiveEffect { onTrigger?: (event: DebuggerEvent) => void _dirtyLevel = DirtyLevels.Dirty - _queryingDirty = false _trackToken!: WeakRef _trackId = 0 _runnings = 0 + _queryings = 0 _depsLength = 0 constructor( @@ -62,7 +62,7 @@ export class ReactiveEffect { public get dirty() { if (this._dirtyLevel === DirtyLevels.ComputedValueMaybeDirty) { this._dirtyLevel = DirtyLevels.NotDirty - this._queryingDirty = true + this._queryings++ pauseTracking() for (const dep of this.deps) { if (dep.computed) { @@ -73,7 +73,7 @@ export class ReactiveEffect { } } resetTracking() - this._queryingDirty = false + this._queryings-- } return this._dirtyLevel >= DirtyLevels.ComputedValueDirty } @@ -304,8 +304,7 @@ export function triggerEffects( effect._dirtyLevel = dirtyLevel if ( lastDirtyLevel === DirtyLevels.NotDirty && - (!effect._queryingDirty || - dirtyLevel !== DirtyLevels.ComputedValueDirty) + (!effect._queryings || dirtyLevel !== DirtyLevels.ComputedValueDirty) ) { if (__DEV__) { effect.onTrigger?.(extend({ effect }, debuggerEventExtraInfo)) From 33643517ef71aeb6e6e85338cd21e07673c10f33 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 21 Oct 2023 01:09:51 +0800 Subject: [PATCH 169/192] chore: add missing dep cleanup --- packages/reactivity/src/effect.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 7163a7100d3..08f30838ab5 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -317,6 +317,9 @@ export function triggerEffects( for (const token of reclaimedTokens) { dep.delete(token) } + if (dep.size === 0) { + dep.cleanup() + } } resetScheduling() } From d3ff222340ad054ec85dd03118a7a658f5c2a7be Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 21 Oct 2023 02:52:09 +0800 Subject: [PATCH 170/192] fix: only auto gc computed --- package.json | 2 +- packages/reactivity/__tests__/gc.spec.ts | 53 ++++++++++++++++++++++++ packages/reactivity/src/effect.ts | 43 ++++++++++++------- packages/runtime-core/src/apiWatch.ts | 4 +- packages/runtime-core/src/renderer.ts | 3 +- 5 files changed, 85 insertions(+), 20 deletions(-) create mode 100644 packages/reactivity/__tests__/gc.spec.ts diff --git a/package.json b/package.json index eb6165924c7..70f070650c7 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "format": "prettier --write --cache \"**/*.[tj]s?(x)\"", "format-check": "prettier --check --cache \"**/*.[tj]s?(x)\"", "test": "vitest", - "test-unit": "vitest -c vitest.unit.config.ts", + "test-unit": "node --expose-gc node_modules/vitest/vitest.mjs -c vitest.unit.config.ts", "test-e2e": "node scripts/build.js vue -f global -d && vitest -c vitest.e2e.config.ts", "test-dts": "run-s build-dts test-dts-only", "test-dts-only": "tsc -p ./packages/dts-test/tsconfig.test.json", diff --git a/packages/reactivity/__tests__/gc.spec.ts b/packages/reactivity/__tests__/gc.spec.ts new file mode 100644 index 00000000000..6915f372d21 --- /dev/null +++ b/packages/reactivity/__tests__/gc.spec.ts @@ -0,0 +1,53 @@ +import { + ComputedRef, + computed, + effect, + shallowRef as ref, + stop +} from '../src/index' + +describe('reactivity/gc', () => { + const gc = () => { + return new Promise(resolve => { + setTimeout(() => { + global.gc!() + resolve() + }) + }) + } + + it('should enabled --expose-gc', () => { + expect(global.gc).not.toBeUndefined() + }) + + // #9233 + it('should release computed cache', async () => { + const src = ref<{} | undefined>({}) + const srcRef = new WeakRef(src.value!) + + let c: ComputedRef | undefined = computed(() => src.value) + + c.value // cache src value + src.value = undefined // release value + c = undefined // release computed + + await gc() + expect(srcRef.deref()).toBeUndefined() + }) + + it('should not release effect', async () => { + const spy = vi.fn() + const src = ref(0) + + effect(() => { + spy() + src.value + }) + + expect(spy).toHaveBeenCalledTimes(1) + + await gc() + src.value++ + expect(spy).toHaveBeenCalledTimes(2) + }) +}) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 08f30838ab5..810d8c129a2 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -4,10 +4,7 @@ import { DirtyLevels, TrackOpTypes, TriggerOpTypes } from './constants' import type { Dep } from './dep' import { EffectScope, recordEffectScope } from './effectScope' -export type EffectScheduler = ( - onScheduled: (cb: () => void) => void, - ...args: any[] -) => any +export type EffectScheduler = (...args: any[]) => any export type DebuggerEvent = { effect: ReactiveEffect @@ -24,6 +21,13 @@ export type DebuggerEventExtraInfo = { export let activeEffect: ReactiveEffect | undefined +class FakeWeakRef { + constructor(public target: T) {} + deref() { + return this.target + } +} + export class ReactiveEffect { active = true deps: Dep[] = [] @@ -53,7 +57,8 @@ export class ReactiveEffect { constructor( public fn: () => T, - public scheduler: EffectScheduler, + public trigger: () => void, + public scheduler?: EffectScheduler, scope?: EffectScope ) { recordEffectScope(this, scope) @@ -88,7 +93,11 @@ export class ReactiveEffect { return this.fn() } if (!this._trackToken) { - this._trackToken = new WeakRef(this) + if (this.scheduler) { + this._trackToken = new FakeWeakRef(this) as any + } else { + this._trackToken = new WeakRef(this) + } } let lastShouldTrack = shouldTrack let lastEffect = activeEffect @@ -180,13 +189,15 @@ export function effect( fn = (fn as ReactiveEffectRunner).effect.fn } - const _effect = new ReactiveEffect(fn, onScheduled => { - onScheduled(() => { + const _effect = new ReactiveEffect( + fn, + () => {}, + () => { if (_effect.dirty) { _effect.run() } - }) - }) + } + ) if (options) { extend(_effect, options) if (options.scope) recordEffectScope(_effect, options.scope) @@ -249,8 +260,8 @@ export function pauseScheduling() { */ export function resetScheduling() { pauseScheduleStack-- - while (!pauseScheduleStack && queueEffectCbs.length) { - queueEffectCbs.shift()!() + while (!pauseScheduleStack && queueEffectSchedulers.length) { + queueEffectSchedulers.shift()!() } } @@ -276,8 +287,7 @@ export function trackEffect( } } -const queueEffectCbs: (() => void)[] = [] -const pushEffectCb = queueEffectCbs.push.bind(queueEffectCbs) +const queueEffectSchedulers: (() => void)[] = [] export function triggerEffects( dep: Dep, @@ -309,7 +319,10 @@ export function triggerEffects( if (__DEV__) { effect.onTrigger?.(extend({ effect }, debuggerEventExtraInfo)) } - effect.scheduler(pushEffectCb) + effect.trigger() + if (effect.scheduler) { + queueEffectSchedulers.push(effect.scheduler) + } } } } diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 80f73ee0a20..b1a69940505 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -361,9 +361,7 @@ function doWatch( schedulerJob = () => queueJob(job) } - const effect = new ReactiveEffect(getter, onScheduled => { - onScheduled(schedulerJob as any) - }) + const effect = new ReactiveEffect(getter, () => {}, schedulerJob as any) if (__DEV__) { effect.onTrack = onTrack diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index e6b5c0e4639..f43ec2fb37d 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -1548,7 +1548,8 @@ function baseCreateRenderer( const schedulerJob = () => queueJob(update) const effect = (instance.effect = new ReactiveEffect( componentUpdateFn, - onScheduled => onScheduled(schedulerJob), + () => {}, + schedulerJob, instance.scope // track it in component's effect scope )) From 8ff2a897396431ca916fa112d715a1b66061f000 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 21 Oct 2023 03:07:24 +0800 Subject: [PATCH 171/192] chore: stubbed WeakRef --- packages/reactivity/src/effect.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 810d8c129a2..02c2ffdabfb 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -1,4 +1,4 @@ -import { extend } from '@vue/shared' +import { extend, getGlobalThis } from '@vue/shared' import type { ComputedRefImpl } from './computed' import { DirtyLevels, TrackOpTypes, TriggerOpTypes } from './constants' import type { Dep } from './dep' @@ -28,6 +28,15 @@ class FakeWeakRef { } } +let _WeakRef = getGlobalThis().WeakRef as typeof WeakRef + +if (!_WeakRef) { + _WeakRef = FakeWeakRef as any + if (__DEV__) { + console.warn(`WeakRef is not available domain-wide and must be stubbed!`) + } +} + export class ReactiveEffect { active = true deps: Dep[] = [] @@ -96,7 +105,7 @@ export class ReactiveEffect { if (this.scheduler) { this._trackToken = new FakeWeakRef(this) as any } else { - this._trackToken = new WeakRef(this) + this._trackToken = new _WeakRef(this) } } let lastShouldTrack = shouldTrack From 9cd9335832ef31f08cd38d6cfd587d056c71c596 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 21 Oct 2023 03:25:31 +0800 Subject: [PATCH 172/192] chore: remove schedulerJob variable --- packages/runtime-core/src/renderer.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index f43ec2fb37d..52f84aaa07f 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -1545,11 +1545,10 @@ function baseCreateRenderer( } // create reactive effect for rendering - const schedulerJob = () => queueJob(update) const effect = (instance.effect = new ReactiveEffect( componentUpdateFn, () => {}, - schedulerJob, + () => queueJob(update), instance.scope // track it in component's effect scope )) From c4829f33ec106bb3b3eb10343fd4f9e21559a3d8 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 21 Oct 2023 13:04:27 +0800 Subject: [PATCH 173/192] Update reactiveEffect.ts --- packages/reactivity/src/reactiveEffect.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/reactivity/src/reactiveEffect.ts b/packages/reactivity/src/reactiveEffect.ts index 8092ebb21f6..715d3d8a4cf 100644 --- a/packages/reactivity/src/reactiveEffect.ts +++ b/packages/reactivity/src/reactiveEffect.ts @@ -1,4 +1,4 @@ -import { isArray, isIntegerKey, isMap } from '@vue/shared' +import { isArray, isIntegerKey, isMap, isSymbol } from '@vue/shared' import { DirtyLevels, TrackOpTypes, TriggerOpTypes } from './constants' import { createDep, Dep } from './dep' import { @@ -82,7 +82,7 @@ export function trigger( } else if (key === 'length' && isArray(target)) { const newLength = Number(newValue) depsMap.forEach((dep, key) => { - if (key === 'length' || key >= newLength) { + if (key === 'length' || (!isSymbol(key) && key >= newLength)) { deps.push(dep) } }) From 526faea0d64e2791809bf03c9f9babc39064582a Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 21 Oct 2023 13:17:56 +0800 Subject: [PATCH 174/192] Update apiWatch.ts --- packages/runtime-core/src/apiWatch.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index b1a69940505..6a458c2e91f 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -6,6 +6,7 @@ import { ReactiveEffect, isReactive, ReactiveFlags, + EffectScheduler, DebuggerOptions, getCurrentScope } from '@vue/reactivity' @@ -348,20 +349,19 @@ function doWatch( // it is allowed to self-trigger (#1727) job.allowRecurse = !!cb - let schedulerJob: SchedulerJob + let scheduler: EffectScheduler if (flush === 'sync') { - schedulerJob = job // the scheduler function gets called directly + scheduler = job as any // the scheduler function gets called directly } else if (flush === 'post') { - schedulerJob = () => - queuePostRenderEffect(job, instance && instance.suspense) + scheduler = () => queuePostRenderEffect(job, instance && instance.suspense) } else { // default: 'pre' job.pre = true if (instance) job.id = instance.uid - schedulerJob = () => queueJob(job) + scheduler = () => queueJob(job) } - const effect = new ReactiveEffect(getter, () => {}, schedulerJob as any) + const effect = new ReactiveEffect(getter, () => {}, scheduler) if (__DEV__) { effect.onTrack = onTrack From 081ccd49ba25be3f39541fa202a760e0398106ec Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 21 Oct 2023 16:26:40 +0800 Subject: [PATCH 175/192] fix: use FinalizationRegistry to cleanup unused computed --- .../reactivity/__tests__/computed.spec.ts | 10 +- packages/reactivity/__tests__/gc.spec.ts | 36 ++++++- .../reactivity/__tests__/readonly.spec.ts | 4 +- packages/reactivity/src/effect.ts | 101 ++++++++++++------ 4 files changed, 110 insertions(+), 41 deletions(-) diff --git a/packages/reactivity/__tests__/computed.spec.ts b/packages/reactivity/__tests__/computed.spec.ts index d9b8f888caf..6cee5845a5f 100644 --- a/packages/reactivity/__tests__/computed.spec.ts +++ b/packages/reactivity/__tests__/computed.spec.ts @@ -11,6 +11,7 @@ import { ITERATE_KEY, TriggerOpTypes } from '../src' +import { depsMap } from '../src/effect' describe('reactivity/computed', () => { it('should return updated value', () => { @@ -400,10 +401,11 @@ describe('reactivity/computed', () => { a.value++ e.value - expect(e.effect.deps.length).toBe(3) - expect(e.effect.deps.indexOf((b as any).dep)).toBe(0) - expect(e.effect.deps.indexOf((d as any).dep)).toBe(1) - expect(e.effect.deps.indexOf((c as any).dep)).toBe(2) + const deps = depsMap.get(e.effect._trackToken) + expect(deps!.length).toBe(3) + expect(deps!.indexOf((b as any).dep)).toBe(0) + expect(deps!.indexOf((d as any).dep)).toBe(1) + expect(deps!.indexOf((c as any).dep)).toBe(2) expect(cSpy).toHaveBeenCalledTimes(2) a.value++ diff --git a/packages/reactivity/__tests__/gc.spec.ts b/packages/reactivity/__tests__/gc.spec.ts index 6915f372d21..88cec4f5efb 100644 --- a/packages/reactivity/__tests__/gc.spec.ts +++ b/packages/reactivity/__tests__/gc.spec.ts @@ -2,9 +2,11 @@ import { ComputedRef, computed, effect, + reactive, shallowRef as ref, - stop + toRaw } from '../src/index' +import { getDepFromReactive } from '../src/reactiveEffect' describe('reactivity/gc', () => { const gc = () => { @@ -35,7 +37,21 @@ describe('reactivity/gc', () => { expect(srcRef.deref()).toBeUndefined() }) - it('should not release effect', async () => { + it('should release reactive property dep', async () => { + const src = reactive({ foo: 1 }) + + let c: ComputedRef | undefined = computed(() => src.foo) + + c.value + expect(getDepFromReactive(toRaw(src), 'foo')).not.toBeUndefined() + + c = undefined + await gc() + await gc() + expect(getDepFromReactive(toRaw(src), 'foo')).toBeUndefined() + }) + + it('should not release effect for ref', async () => { const spy = vi.fn() const src = ref(0) @@ -50,4 +66,20 @@ describe('reactivity/gc', () => { src.value++ expect(spy).toHaveBeenCalledTimes(2) }) + + it('should not release effect for reactive', async () => { + const spy = vi.fn() + const src = reactive({ foo: 1 }) + + effect(() => { + spy() + src.foo + }) + + expect(spy).toHaveBeenCalledTimes(1) + + await gc() + src.foo++ + expect(spy).toHaveBeenCalledTimes(2) + }) }) diff --git a/packages/reactivity/__tests__/readonly.spec.ts b/packages/reactivity/__tests__/readonly.spec.ts index d0c91f0fb9f..41e0ae0a225 100644 --- a/packages/reactivity/__tests__/readonly.spec.ts +++ b/packages/reactivity/__tests__/readonly.spec.ts @@ -10,6 +10,7 @@ import { isProxy, computed } from '../src' +import { depsMap } from '../src/effect' /** * @see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html @@ -393,7 +394,8 @@ describe('reactivity/readonly', () => { const eff = effect(() => { roArr.includes(2) }) - expect(eff.effect.deps.length).toBe(0) + const deps = depsMap.get(eff.effect._trackToken) + expect(deps).toBeUndefined() }) test('readonly should track and trigger if wrapping reactive original (collection)', () => { diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 02c2ffdabfb..5e5973d3d84 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -21,7 +21,35 @@ export type DebuggerEventExtraInfo = { export let activeEffect: ReactiveEffect | undefined -class FakeWeakRef { +let _FinalizationRegistry = getGlobalThis() + .FinalizationRegistry as typeof FinalizationRegistry + +if (!_FinalizationRegistry) { + _FinalizationRegistry = class FakeFinalizationRegistry { + register() {} + unregister() {} + } as any + if (__DEV__) { + console.warn(`FinalizationRegistry is not available in this environment.`) + } +} + +const registry = new _FinalizationRegistry>( + trackToken => { + const deps = depsMap.get(trackToken) + if (deps) { + for (const dep of deps) { + dep.delete(trackToken) + if (dep.size === 0) { + dep.cleanup() + } + } + deps.length = 0 + } + } +) + +class StrongRef { constructor(public target: T) {} deref() { return this.target @@ -31,15 +59,16 @@ class FakeWeakRef { let _WeakRef = getGlobalThis().WeakRef as typeof WeakRef if (!_WeakRef) { - _WeakRef = FakeWeakRef as any + _WeakRef = StrongRef as any if (__DEV__) { - console.warn(`WeakRef is not available domain-wide and must be stubbed!`) + console.warn(`WeakRef is not available in this environment.`) } } +export const depsMap = new WeakMap, Dep[]>() + export class ReactiveEffect { active = true - deps: Dep[] = [] /** * Can be attached after creation @@ -76,18 +105,23 @@ export class ReactiveEffect { public get dirty() { if (this._dirtyLevel === DirtyLevels.ComputedValueMaybeDirty) { this._dirtyLevel = DirtyLevels.NotDirty - this._queryings++ - pauseTracking() - for (const dep of this.deps) { - if (dep.computed) { - triggerComputed(dep.computed) - if (this._dirtyLevel >= DirtyLevels.ComputedValueDirty) { - break + if (this._trackToken) { + const deps = depsMap.get(this._trackToken) + if (deps) { + this._queryings++ + pauseTracking() + for (const dep of deps) { + if (dep.computed) { + triggerComputed(dep.computed) + if (this._dirtyLevel >= DirtyLevels.ComputedValueDirty) { + break + } + } } + resetTracking() + this._queryings-- } } - resetTracking() - this._queryings-- } return this._dirtyLevel >= DirtyLevels.ComputedValueDirty } @@ -103,9 +137,10 @@ export class ReactiveEffect { } if (!this._trackToken) { if (this.scheduler) { - this._trackToken = new FakeWeakRef(this) as any + this._trackToken = new StrongRef(this) as any } else { this._trackToken = new _WeakRef(this) + registry.register(this, this._trackToken, this) } } let lastShouldTrack = shouldTrack @@ -114,7 +149,7 @@ export class ReactiveEffect { shouldTrack = true activeEffect = this this._runnings++ - cleanupEffect(this) + preCleanupEffect(this) return this.fn() } finally { postCleanupEffect(this) @@ -126,7 +161,7 @@ export class ReactiveEffect { stop() { if (this.active) { - cleanupEffect(this) + preCleanupEffect(this) postCleanupEffect(this) this.onStop?.() this.active = false @@ -138,17 +173,22 @@ function triggerComputed(computed: ComputedRefImpl) { return computed.value } -function cleanupEffect(effect: ReactiveEffect) { +function preCleanupEffect(effect: ReactiveEffect) { effect._trackId++ effect._depsLength = 0 } function postCleanupEffect(effect: ReactiveEffect) { - if (effect.deps.length > effect._depsLength) { - for (let i = effect._depsLength; i < effect.deps.length; i++) { - cleanupDepEffect(effect.deps[i], effect) + if (effect._trackToken) { + const deps = depsMap.get(effect._trackToken) + if (deps) { + if (deps.length > effect._depsLength) { + for (let i = effect._depsLength; i < deps.length; i++) { + cleanupDepEffect(deps[i], effect) + } + deps.length = effect._depsLength + } } - effect.deps.length = effect._depsLength } } @@ -281,12 +321,16 @@ export function trackEffect( ) { if (dep.get(effect._trackToken) !== effect._trackId) { dep.set(effect._trackToken, effect._trackId) - const oldDep = effect.deps[effect._depsLength] + let deps = depsMap.get(effect._trackToken) + if (!deps) { + depsMap.set(effect._trackToken, (deps = [])) + } + const oldDep = deps[effect._depsLength] if (oldDep !== dep) { if (oldDep) { cleanupDepEffect(oldDep, effect) } - effect.deps[effect._depsLength++] = dep + deps[effect._depsLength++] = dep } else { effect._depsLength++ } @@ -304,12 +348,9 @@ export function triggerEffects( debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { pauseScheduling() - let reclaimedTokens: WeakRef[] | undefined for (const trackToken of dep.keys()) { const effect = trackToken.deref() if (!effect) { - reclaimedTokens ??= [] - reclaimedTokens.push(trackToken) continue } if (!effect.allowRecurse && effect._runnings) { @@ -335,13 +376,5 @@ export function triggerEffects( } } } - if (reclaimedTokens) { - for (const token of reclaimedTokens) { - dep.delete(token) - } - if (dep.size === 0) { - dep.cleanup() - } - } resetScheduling() } From c388d135f3a26cb5fb319f31b10566c443ef613b Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sat, 21 Oct 2023 23:08:03 +0800 Subject: [PATCH 176/192] Update effect.ts [skip ci] --- packages/reactivity/src/effect.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 5e5973d3d84..2b2cb909662 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -181,13 +181,11 @@ function preCleanupEffect(effect: ReactiveEffect) { function postCleanupEffect(effect: ReactiveEffect) { if (effect._trackToken) { const deps = depsMap.get(effect._trackToken) - if (deps) { - if (deps.length > effect._depsLength) { - for (let i = effect._depsLength; i < deps.length; i++) { - cleanupDepEffect(deps[i], effect) - } - deps.length = effect._depsLength + if (deps && deps.length > effect._depsLength) { + for (let i = effect._depsLength; i < deps.length; i++) { + cleanupDepEffect(deps[i], effect) } + deps.length = effect._depsLength } } } From c6b9bd281889d154e495f6d05058be3fed78eceb Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 22 Oct 2023 01:12:13 +0800 Subject: [PATCH 177/192] test: disable threads for `FinalizationRegistry.register` --- package.json | 2 +- packages/reactivity/__tests__/gc.spec.ts | 6 +----- vitest.config.ts | 5 +++-- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index cb9f73cc3a4..d903294ab1e 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "format": "prettier --write --cache \"**/*.[tj]s?(x)\"", "format-check": "prettier --check --cache \"**/*.[tj]s?(x)\"", "test": "vitest", - "test-unit": "node --expose-gc node_modules/vitest/vitest.mjs -c vitest.unit.config.ts", + "test-unit": "vitest -c vitest.unit.config.ts", "test-e2e": "node scripts/build.js vue -f global -d && vitest -c vitest.e2e.config.ts", "test-dts": "run-s build-dts test-dts-only", "test-dts-only": "tsc -p ./packages/dts-test/tsconfig.test.json", diff --git a/packages/reactivity/__tests__/gc.spec.ts b/packages/reactivity/__tests__/gc.spec.ts index 88cec4f5efb..7676a0e12d0 100644 --- a/packages/reactivity/__tests__/gc.spec.ts +++ b/packages/reactivity/__tests__/gc.spec.ts @@ -8,7 +8,7 @@ import { } from '../src/index' import { getDepFromReactive } from '../src/reactiveEffect' -describe('reactivity/gc', () => { +describe.skipIf(!global.gc)('reactivity/gc', () => { const gc = () => { return new Promise(resolve => { setTimeout(() => { @@ -18,10 +18,6 @@ describe('reactivity/gc', () => { }) } - it('should enabled --expose-gc', () => { - expect(global.gc).not.toBeUndefined() - }) - // #9233 it('should release computed cache', async () => { const src = ref<{} | undefined>({}) diff --git a/vitest.config.ts b/vitest.config.ts index e5d5f59345f..38bd6a469b0 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -22,8 +22,9 @@ export default defineConfig({ }, test: { globals: true, - // disable threads on GH actions to speed it up - threads: !process.env.GITHUB_ACTIONS, + // threads must be disabled because FinalizationRegistry.register is used in the code + // and multi-threading causes thread locks for some reasons + threads: false, setupFiles: 'scripts/setupVitest.ts', environmentMatchGlobs: [ ['packages/{vue,vue-compat,runtime-dom}/**', 'jsdom'] From 78c50e3be502f63eef2c660002abb0ed6e85843e Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 22 Oct 2023 01:34:13 +0800 Subject: [PATCH 178/192] chore: remove `@internal` for fix dts build --- packages/reactivity/src/effect.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 2b2cb909662..d2002bb29b0 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -295,16 +295,10 @@ export function resetTracking() { shouldTrack = last === undefined ? true : last } -/** - * @internal - */ export function pauseScheduling() { pauseScheduleStack++ } -/** - * @internal - */ export function resetScheduling() { pauseScheduleStack-- while (!pauseScheduleStack && queueEffectSchedulers.length) { From 3353fb4368c168218809b3bbb5c614a9fef778ee Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 22 Oct 2023 03:00:53 +0800 Subject: [PATCH 179/192] chore: remove FakeFinalizationRegistry --- .../reactivity/__tests__/readonly.spec.ts | 3 +- packages/reactivity/src/effect.ts | 43 ++++++++----------- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/packages/reactivity/__tests__/readonly.spec.ts b/packages/reactivity/__tests__/readonly.spec.ts index 41e0ae0a225..034a58923c4 100644 --- a/packages/reactivity/__tests__/readonly.spec.ts +++ b/packages/reactivity/__tests__/readonly.spec.ts @@ -394,8 +394,7 @@ describe('reactivity/readonly', () => { const eff = effect(() => { roArr.includes(2) }) - const deps = depsMap.get(eff.effect._trackToken) - expect(deps).toBeUndefined() + expect(depsMap.get(eff.effect._trackToken)).toBeUndefined() }) test('readonly should track and trigger if wrapping reactive original (collection)', () => { diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index d2002bb29b0..890e35b4d28 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -21,33 +21,28 @@ export type DebuggerEventExtraInfo = { export let activeEffect: ReactiveEffect | undefined -let _FinalizationRegistry = getGlobalThis() - .FinalizationRegistry as typeof FinalizationRegistry - -if (!_FinalizationRegistry) { - _FinalizationRegistry = class FakeFinalizationRegistry { - register() {} - unregister() {} - } as any - if (__DEV__) { - console.warn(`FinalizationRegistry is not available in this environment.`) - } +let _FinalizationRegistry = getGlobalThis().FinalizationRegistry as + | typeof FinalizationRegistry + | undefined + +if (!_FinalizationRegistry && __DEV__) { + console.warn(`FinalizationRegistry is not available in this environment.`) } -const registry = new _FinalizationRegistry>( - trackToken => { - const deps = depsMap.get(trackToken) - if (deps) { - for (const dep of deps) { - dep.delete(trackToken) - if (dep.size === 0) { - dep.cleanup() +const registry = _FinalizationRegistry + ? new _FinalizationRegistry>(trackToken => { + const deps = depsMap.get(trackToken) + if (deps) { + for (const dep of deps) { + dep.delete(trackToken) + if (dep.size === 0) { + dep.cleanup() + } } + deps.length = 0 } - deps.length = 0 - } - } -) + }) + : undefined class StrongRef { constructor(public target: T) {} @@ -140,7 +135,7 @@ export class ReactiveEffect { this._trackToken = new StrongRef(this) as any } else { this._trackToken = new _WeakRef(this) - registry.register(this, this._trackToken, this) + registry?.register(this, this._trackToken, this) } } let lastShouldTrack = shouldTrack From c53bd48de5056b838a913bc9082a076db2f30ac6 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 22 Oct 2023 03:02:52 +0800 Subject: [PATCH 180/192] chore: use const --- packages/reactivity/src/effect.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 890e35b4d28..a5f8557527e 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -21,7 +21,7 @@ export type DebuggerEventExtraInfo = { export let activeEffect: ReactiveEffect | undefined -let _FinalizationRegistry = getGlobalThis().FinalizationRegistry as +const _FinalizationRegistry = getGlobalThis().FinalizationRegistry as | typeof FinalizationRegistry | undefined From ee17f376dbbcb0ef83e69922c1074e586cae8628 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 22 Oct 2023 03:18:45 +0800 Subject: [PATCH 181/192] fix: create trackToken when actually track [skip ci] --- .../reactivity/__tests__/computed.spec.ts | 2 +- .../reactivity/__tests__/readonly.spec.ts | 2 +- packages/reactivity/src/effect.ts | 39 ++++++++++--------- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/packages/reactivity/__tests__/computed.spec.ts b/packages/reactivity/__tests__/computed.spec.ts index 6cee5845a5f..d34ed5a7392 100644 --- a/packages/reactivity/__tests__/computed.spec.ts +++ b/packages/reactivity/__tests__/computed.spec.ts @@ -401,7 +401,7 @@ describe('reactivity/computed', () => { a.value++ e.value - const deps = depsMap.get(e.effect._trackToken) + const deps = depsMap.get(e.effect._trackToken!) expect(deps!.length).toBe(3) expect(deps!.indexOf((b as any).dep)).toBe(0) expect(deps!.indexOf((d as any).dep)).toBe(1) diff --git a/packages/reactivity/__tests__/readonly.spec.ts b/packages/reactivity/__tests__/readonly.spec.ts index 034a58923c4..0444aedcad7 100644 --- a/packages/reactivity/__tests__/readonly.spec.ts +++ b/packages/reactivity/__tests__/readonly.spec.ts @@ -394,7 +394,7 @@ describe('reactivity/readonly', () => { const eff = effect(() => { roArr.includes(2) }) - expect(depsMap.get(eff.effect._trackToken)).toBeUndefined() + expect(depsMap.get(eff.effect._trackToken!)).toBeUndefined() }) test('readonly should track and trigger if wrapping reactive original (collection)', () => { diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index a5f8557527e..491e981b9b4 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -82,7 +82,7 @@ export class ReactiveEffect { onTrigger?: (event: DebuggerEvent) => void _dirtyLevel = DirtyLevels.Dirty - _trackToken!: WeakRef + _trackToken?: WeakRef _trackId = 0 _runnings = 0 _queryings = 0 @@ -130,14 +130,6 @@ export class ReactiveEffect { if (!this.active) { return this.fn() } - if (!this._trackToken) { - if (this.scheduler) { - this._trackToken = new StrongRef(this) as any - } else { - this._trackToken = new _WeakRef(this) - registry?.register(this, this._trackToken, this) - } - } let lastShouldTrack = shouldTrack let lastEffect = activeEffect try { @@ -186,11 +178,13 @@ function postCleanupEffect(effect: ReactiveEffect) { } function cleanupDepEffect(dep: Dep, effect: ReactiveEffect) { - const trackId = dep.get(effect._trackToken) - if (trackId !== undefined && effect._trackId !== trackId) { - dep.delete(effect._trackToken) - if (dep.size === 0) { - dep.cleanup() + if (effect._trackToken) { + const trackId = dep.get(effect._trackToken) + if (trackId !== undefined && effect._trackId !== trackId) { + dep.delete(effect._trackToken) + if (dep.size === 0) { + dep.cleanup() + } } } } @@ -306,11 +300,20 @@ export function trackEffect( dep: Dep, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { - if (dep.get(effect._trackToken) !== effect._trackId) { - dep.set(effect._trackToken, effect._trackId) - let deps = depsMap.get(effect._trackToken) + if (!effect._trackToken) { + if (effect.scheduler) { + effect._trackToken = new StrongRef(effect) as any + } else { + effect._trackToken = new _WeakRef(effect) + registry?.register(effect, effect._trackToken, effect) + } + } + const trackToken = effect._trackToken! + if (dep.get(trackToken) !== effect._trackId) { + dep.set(trackToken, effect._trackId) + let deps = depsMap.get(trackToken) if (!deps) { - depsMap.set(effect._trackToken, (deps = [])) + depsMap.set(trackToken, (deps = [])) } const oldDep = deps[effect._depsLength] if (oldDep !== dep) { From 2bbb45115f75788daf2e150e9b9d07caa4f84eda Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 22 Oct 2023 04:04:26 +0800 Subject: [PATCH 182/192] chore: remove StrongRef --- packages/reactivity/src/dep.ts | 2 +- packages/reactivity/src/effect.ts | 31 ++++++++++++++----------------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/packages/reactivity/src/dep.ts b/packages/reactivity/src/dep.ts index cb9f7494c5f..f1f3034c3b3 100644 --- a/packages/reactivity/src/dep.ts +++ b/packages/reactivity/src/dep.ts @@ -1,7 +1,7 @@ import type { ReactiveEffect } from './effect' import type { ComputedRefImpl } from './computed' -export type Dep = Map, number> & { +export type Dep = Map | ReactiveEffect, number> & { cleanup: () => void computed?: ComputedRefImpl } diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 491e981b9b4..10c3b6d4b9e 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -44,23 +44,16 @@ const registry = _FinalizationRegistry }) : undefined -class StrongRef { - constructor(public target: T) {} - deref() { - return this.target - } -} - -let _WeakRef = getGlobalThis().WeakRef as typeof WeakRef +let _WeakRef = getGlobalThis().WeakRef as typeof WeakRef | undefined -if (!_WeakRef) { - _WeakRef = StrongRef as any - if (__DEV__) { - console.warn(`WeakRef is not available in this environment.`) - } +if (!_WeakRef && __DEV__) { + console.warn(`WeakRef is not available in this environment.`) } -export const depsMap = new WeakMap, Dep[]>() +export const depsMap = new WeakMap< + WeakRef | ReactiveEffect, + Dep[] +>() export class ReactiveEffect { active = true @@ -82,7 +75,7 @@ export class ReactiveEffect { onTrigger?: (event: DebuggerEvent) => void _dirtyLevel = DirtyLevels.Dirty - _trackToken?: WeakRef + _trackToken?: WeakRef | ReactiveEffect _trackId = 0 _runnings = 0 _queryings = 0 @@ -154,6 +147,10 @@ export class ReactiveEffect { this.active = false } } + + deref() { + return this + } } function triggerComputed(computed: ComputedRefImpl) { @@ -301,8 +298,8 @@ export function trackEffect( debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { if (!effect._trackToken) { - if (effect.scheduler) { - effect._trackToken = new StrongRef(effect) as any + if (effect.scheduler || !_WeakRef) { + effect._trackToken = effect } else { effect._trackToken = new _WeakRef(effect) registry?.register(effect, effect._trackToken, effect) From 3540dd127aa812fcad819ab66362ddd155fc866a Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 22 Oct 2023 04:27:21 +0800 Subject: [PATCH 183/192] chore: add TrackToken type [skip ci] --- packages/reactivity/src/dep.ts | 4 +++- packages/reactivity/src/effect.ts | 9 +++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/reactivity/src/dep.ts b/packages/reactivity/src/dep.ts index f1f3034c3b3..f8b060dc954 100644 --- a/packages/reactivity/src/dep.ts +++ b/packages/reactivity/src/dep.ts @@ -1,7 +1,9 @@ import type { ReactiveEffect } from './effect' import type { ComputedRefImpl } from './computed' -export type Dep = Map | ReactiveEffect, number> & { +export type TrackToken = WeakRef | ReactiveEffect + +export type Dep = Map & { cleanup: () => void computed?: ComputedRefImpl } diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 10c3b6d4b9e..678d5aee6fc 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -1,7 +1,7 @@ import { extend, getGlobalThis } from '@vue/shared' import type { ComputedRefImpl } from './computed' import { DirtyLevels, TrackOpTypes, TriggerOpTypes } from './constants' -import type { Dep } from './dep' +import type { Dep, TrackToken } from './dep' import { EffectScope, recordEffectScope } from './effectScope' export type EffectScheduler = (...args: any[]) => any @@ -50,10 +50,7 @@ if (!_WeakRef && __DEV__) { console.warn(`WeakRef is not available in this environment.`) } -export const depsMap = new WeakMap< - WeakRef | ReactiveEffect, - Dep[] ->() +export const depsMap = new WeakMap() export class ReactiveEffect { active = true @@ -75,7 +72,7 @@ export class ReactiveEffect { onTrigger?: (event: DebuggerEvent) => void _dirtyLevel = DirtyLevels.Dirty - _trackToken?: WeakRef | ReactiveEffect + _trackToken?: TrackToken _trackId = 0 _runnings = 0 _queryings = 0 From 6799da059f050a6d0bef8a558e1e1bd1523422d9 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 22 Oct 2023 04:28:16 +0800 Subject: [PATCH 184/192] chore: move TrackToken type [skip ci] --- packages/reactivity/src/dep.ts | 4 +--- packages/reactivity/src/effect.ts | 4 +++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/reactivity/src/dep.ts b/packages/reactivity/src/dep.ts index f8b060dc954..ee83e116611 100644 --- a/packages/reactivity/src/dep.ts +++ b/packages/reactivity/src/dep.ts @@ -1,8 +1,6 @@ -import type { ReactiveEffect } from './effect' +import type { TrackToken } from './effect' import type { ComputedRefImpl } from './computed' -export type TrackToken = WeakRef | ReactiveEffect - export type Dep = Map & { cleanup: () => void computed?: ComputedRefImpl diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 678d5aee6fc..55c5e1d8ac5 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -1,7 +1,7 @@ import { extend, getGlobalThis } from '@vue/shared' import type { ComputedRefImpl } from './computed' import { DirtyLevels, TrackOpTypes, TriggerOpTypes } from './constants' -import type { Dep, TrackToken } from './dep' +import type { Dep } from './dep' import { EffectScope, recordEffectScope } from './effectScope' export type EffectScheduler = (...args: any[]) => any @@ -52,6 +52,8 @@ if (!_WeakRef && __DEV__) { export const depsMap = new WeakMap() +export type TrackToken = WeakRef | ReactiveEffect + export class ReactiveEffect { active = true From 1e2db5b580aaf954f906fb53b4484de54945175b Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Sun, 22 Oct 2023 20:47:24 +0800 Subject: [PATCH 185/192] chore: sort code --- packages/reactivity/src/effect.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 55c5e1d8ac5..66b69872184 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -24,10 +24,14 @@ export let activeEffect: ReactiveEffect | undefined const _FinalizationRegistry = getGlobalThis().FinalizationRegistry as | typeof FinalizationRegistry | undefined +const _WeakRef = getGlobalThis().WeakRef as typeof WeakRef | undefined if (!_FinalizationRegistry && __DEV__) { console.warn(`FinalizationRegistry is not available in this environment.`) } +if (!_WeakRef && __DEV__) { + console.warn(`WeakRef is not available in this environment.`) +} const registry = _FinalizationRegistry ? new _FinalizationRegistry>(trackToken => { @@ -44,12 +48,6 @@ const registry = _FinalizationRegistry }) : undefined -let _WeakRef = getGlobalThis().WeakRef as typeof WeakRef | undefined - -if (!_WeakRef && __DEV__) { - console.warn(`WeakRef is not available in this environment.`) -} - export const depsMap = new WeakMap() export type TrackToken = WeakRef | ReactiveEffect From e888f585d398469ca0d9af8725c83a8dd2902e77 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Wed, 25 Oct 2023 10:59:54 +0800 Subject: [PATCH 186/192] asdf --- .../__tests__/deferredComputed.spec.ts | 1 + packages/reactivity/src/ref.ts | 61 ++++++++----------- vitest.config.ts | 3 +- 3 files changed, 29 insertions(+), 36 deletions(-) diff --git a/packages/reactivity/__tests__/deferredComputed.spec.ts b/packages/reactivity/__tests__/deferredComputed.spec.ts index 6a75daa98e6..8e78ba959c3 100644 --- a/packages/reactivity/__tests__/deferredComputed.spec.ts +++ b/packages/reactivity/__tests__/deferredComputed.spec.ts @@ -15,6 +15,7 @@ describe('deferred computed', () => { expect(spy).toHaveBeenCalledTimes(1) src.value = 3 + src.value = 5 // should trigger because latest value changes expect(spy).toHaveBeenCalledTimes(2) }) diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 90c564ed8b5..5a4dd710eab 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -41,30 +41,21 @@ type RefBase = { export function trackRefValue(ref: RefBase) { if (shouldTrack && activeEffect) { ref = toRaw(ref) - if (__DEV__) { - trackEffect( - activeEffect, - ref.dep || - (ref.dep = createDep( - () => (ref.dep = undefined), - ref instanceof ComputedRefImpl ? ref : undefined - )), - { - target: ref, - type: TrackOpTypes.GET, - key: 'value' - } - ) - } else { - trackEffect( - activeEffect, - ref.dep || - (ref.dep = createDep( - () => (ref.dep = undefined), - ref instanceof ComputedRefImpl ? ref : undefined - )) - ) - } + trackEffect( + activeEffect, + ref.dep || + (ref.dep = createDep( + () => (ref.dep = undefined), + ref instanceof ComputedRefImpl ? ref : undefined + )), + __DEV__ + ? { + target: ref, + type: TrackOpTypes.GET, + key: 'value' + } + : void 0 + ) } } @@ -76,16 +67,18 @@ export function triggerRefValue( ref = toRaw(ref) const dep = ref.dep if (dep) { - if (__DEV__) { - triggerEffects(dep, dirtyLevel, { - target: ref, - type: TriggerOpTypes.SET, - key: 'value', - newValue: newVal - }) - } else { - triggerEffects(dep, dirtyLevel) - } + triggerEffects( + dep, + dirtyLevel, + __DEV__ + ? { + target: ref, + type: TriggerOpTypes.SET, + key: 'value', + newValue: newVal + } + : void 0 + ) } } diff --git a/vitest.config.ts b/vitest.config.ts index 38bd6a469b0..a829c130725 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -22,8 +22,7 @@ export default defineConfig({ }, test: { globals: true, - // threads must be disabled because FinalizationRegistry.register is used in the code - // and multi-threading causes thread locks for some reasons + // threads must be disabled as FinalizationRegistry causes vitest zombie process somehow threads: false, setupFiles: 'scripts/setupVitest.ts', environmentMatchGlobs: [ From 1ce784b1f98fc90c46aa132e6d64fd3e982b3854 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Wed, 25 Oct 2023 11:01:28 +0800 Subject: [PATCH 187/192] qwer --- packages/reactivity/src/reactiveEffect.ts | 46 ++++++++++++----------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/packages/reactivity/src/reactiveEffect.ts b/packages/reactivity/src/reactiveEffect.ts index 715d3d8a4cf..d3474db3da1 100644 --- a/packages/reactivity/src/reactiveEffect.ts +++ b/packages/reactivity/src/reactiveEffect.ts @@ -40,15 +40,17 @@ export function track(target: object, type: TrackOpTypes, key: unknown) { if (!dep) { depsMap.set(key, (dep = createDep(() => depsMap!.delete(key)))) } - if (__DEV__) { - trackEffect(activeEffect, dep, { - target, - type, - key - }) - } else { - trackEffect(activeEffect, dep) - } + trackEffect( + activeEffect, + dep, + __DEV__ + ? { + target, + type, + key + } + : void 0 + ) } } @@ -124,18 +126,20 @@ export function trigger( 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) - } + triggerEffects( + dep, + DirtyLevels.Dirty, + __DEV__ + ? { + target, + type, + key, + newValue, + oldValue, + oldTarget + } + : void 0 + ) } } resetScheduling() From b98b6b89914b17fbd9e3122a168819beb7927929 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 26 Oct 2023 12:16:08 +0900 Subject: [PATCH 188/192] chore: avoid exposing internal properties on ReactiveEffect Previously it also causes use of WeakRef in types and can break projects with tsconfig that does not have the global type --- packages/reactivity/src/effect.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 66b69872184..8b9a7e7b5b5 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -71,11 +71,29 @@ export class ReactiveEffect { // dev only onTrigger?: (event: DebuggerEvent) => void + /** + * @internal + */ _dirtyLevel = DirtyLevels.Dirty + /** + * @internal + */ _trackToken?: TrackToken + /** + * @internal + */ _trackId = 0 + /** + * @internal + */ _runnings = 0 + /** + * @internal + */ _queryings = 0 + /** + * @internal + */ _depsLength = 0 constructor( From 600966b4d0356a487575cac863fa3e0839abd3c6 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 26 Oct 2023 12:53:05 +0900 Subject: [PATCH 189/192] refactor: use NOOP for empty triggers --- packages/reactivity/src/effect.ts | 14 +++++--------- packages/runtime-core/src/apiWatch.ts | 2 +- packages/runtime-core/src/renderer.ts | 2 +- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 8b9a7e7b5b5..287f3158c85 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -1,4 +1,4 @@ -import { extend, getGlobalThis } from '@vue/shared' +import { NOOP, extend, getGlobalThis } from '@vue/shared' import type { ComputedRefImpl } from './computed' import { DirtyLevels, TrackOpTypes, TriggerOpTypes } from './constants' import type { Dep } from './dep' @@ -237,15 +237,11 @@ export function effect( fn = (fn as ReactiveEffectRunner).effect.fn } - const _effect = new ReactiveEffect( - fn, - () => {}, - () => { - if (_effect.dirty) { - _effect.run() - } + const _effect = new ReactiveEffect(fn, NOOP, () => { + if (_effect.dirty) { + _effect.run() } - ) + }) if (options) { extend(_effect, options) if (options.scope) recordEffectScope(_effect, options.scope) diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 6a458c2e91f..23927003d71 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -361,7 +361,7 @@ function doWatch( scheduler = () => queueJob(job) } - const effect = new ReactiveEffect(getter, () => {}, scheduler) + const effect = new ReactiveEffect(getter, NOOP, scheduler) if (__DEV__) { effect.onTrack = onTrack diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 52f84aaa07f..7a67abf7bfd 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -1547,7 +1547,7 @@ function baseCreateRenderer( // create reactive effect for rendering const effect = (instance.effect = new ReactiveEffect( componentUpdateFn, - () => {}, + NOOP, () => queueJob(update), instance.scope // track it in component's effect scope )) From 265c22fe9cab9f1bcb7382e36d858a687bbb5d61 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 26 Oct 2023 17:40:58 +0800 Subject: [PATCH 190/192] refactor: unuse `FinalizationRegistry`, `WeakRef` --- .../reactivity/__tests__/computed.spec.ts | 10 +- .../reactivity/__tests__/readonly.spec.ts | 3 +- packages/reactivity/src/effect.ts | 103 ++++-------------- 3 files changed, 29 insertions(+), 87 deletions(-) diff --git a/packages/reactivity/__tests__/computed.spec.ts b/packages/reactivity/__tests__/computed.spec.ts index d34ed5a7392..d9b8f888caf 100644 --- a/packages/reactivity/__tests__/computed.spec.ts +++ b/packages/reactivity/__tests__/computed.spec.ts @@ -11,7 +11,6 @@ import { ITERATE_KEY, TriggerOpTypes } from '../src' -import { depsMap } from '../src/effect' describe('reactivity/computed', () => { it('should return updated value', () => { @@ -401,11 +400,10 @@ describe('reactivity/computed', () => { a.value++ e.value - const deps = depsMap.get(e.effect._trackToken!) - expect(deps!.length).toBe(3) - expect(deps!.indexOf((b as any).dep)).toBe(0) - expect(deps!.indexOf((d as any).dep)).toBe(1) - expect(deps!.indexOf((c as any).dep)).toBe(2) + expect(e.effect.deps.length).toBe(3) + expect(e.effect.deps.indexOf((b as any).dep)).toBe(0) + expect(e.effect.deps.indexOf((d as any).dep)).toBe(1) + expect(e.effect.deps.indexOf((c as any).dep)).toBe(2) expect(cSpy).toHaveBeenCalledTimes(2) a.value++ diff --git a/packages/reactivity/__tests__/readonly.spec.ts b/packages/reactivity/__tests__/readonly.spec.ts index 0444aedcad7..d0c91f0fb9f 100644 --- a/packages/reactivity/__tests__/readonly.spec.ts +++ b/packages/reactivity/__tests__/readonly.spec.ts @@ -10,7 +10,6 @@ import { isProxy, computed } from '../src' -import { depsMap } from '../src/effect' /** * @see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html @@ -394,7 +393,7 @@ describe('reactivity/readonly', () => { const eff = effect(() => { roArr.includes(2) }) - expect(depsMap.get(eff.effect._trackToken!)).toBeUndefined() + expect(eff.effect.deps.length).toBe(0) }) test('readonly should track and trigger if wrapping reactive original (collection)', () => { diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 287f3158c85..4d852615e06 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -1,4 +1,4 @@ -import { NOOP, extend, getGlobalThis } from '@vue/shared' +import { NOOP, extend } from '@vue/shared' import type { ComputedRefImpl } from './computed' import { DirtyLevels, TrackOpTypes, TriggerOpTypes } from './constants' import type { Dep } from './dep' @@ -21,39 +21,11 @@ export type DebuggerEventExtraInfo = { export let activeEffect: ReactiveEffect | undefined -const _FinalizationRegistry = getGlobalThis().FinalizationRegistry as - | typeof FinalizationRegistry - | undefined -const _WeakRef = getGlobalThis().WeakRef as typeof WeakRef | undefined - -if (!_FinalizationRegistry && __DEV__) { - console.warn(`FinalizationRegistry is not available in this environment.`) -} -if (!_WeakRef && __DEV__) { - console.warn(`WeakRef is not available in this environment.`) -} - -const registry = _FinalizationRegistry - ? new _FinalizationRegistry>(trackToken => { - const deps = depsMap.get(trackToken) - if (deps) { - for (const dep of deps) { - dep.delete(trackToken) - if (dep.size === 0) { - dep.cleanup() - } - } - deps.length = 0 - } - }) - : undefined - -export const depsMap = new WeakMap() - export type TrackToken = WeakRef | ReactiveEffect export class ReactiveEffect { active = true + deps: Dep[] = [] /** * Can be attached after creation @@ -75,10 +47,6 @@ export class ReactiveEffect { * @internal */ _dirtyLevel = DirtyLevels.Dirty - /** - * @internal - */ - _trackToken?: TrackToken /** * @internal */ @@ -108,23 +76,18 @@ export class ReactiveEffect { public get dirty() { if (this._dirtyLevel === DirtyLevels.ComputedValueMaybeDirty) { this._dirtyLevel = DirtyLevels.NotDirty - if (this._trackToken) { - const deps = depsMap.get(this._trackToken) - if (deps) { - this._queryings++ - pauseTracking() - for (const dep of deps) { - if (dep.computed) { - triggerComputed(dep.computed) - if (this._dirtyLevel >= DirtyLevels.ComputedValueDirty) { - break - } - } + this._queryings++ + pauseTracking() + for (const dep of this.deps) { + if (dep.computed) { + triggerComputed(dep.computed) + if (this._dirtyLevel >= DirtyLevels.ComputedValueDirty) { + break } - resetTracking() - this._queryings-- } } + resetTracking() + this._queryings-- } return this._dirtyLevel >= DirtyLevels.ComputedValueDirty } @@ -178,25 +141,20 @@ function preCleanupEffect(effect: ReactiveEffect) { } function postCleanupEffect(effect: ReactiveEffect) { - if (effect._trackToken) { - const deps = depsMap.get(effect._trackToken) - if (deps && deps.length > effect._depsLength) { - for (let i = effect._depsLength; i < deps.length; i++) { - cleanupDepEffect(deps[i], effect) - } - deps.length = effect._depsLength + if (effect.deps && effect.deps.length > effect._depsLength) { + 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) { - if (effect._trackToken) { - const trackId = dep.get(effect._trackToken) - if (trackId !== undefined && effect._trackId !== trackId) { - dep.delete(effect._trackToken) - if (dep.size === 0) { - dep.cleanup() - } + const trackId = dep.get(effect) + if (trackId !== undefined && effect._trackId !== trackId) { + dep.delete(effect) + if (dep.size === 0) { + dep.cleanup() } } } @@ -308,27 +266,14 @@ export function trackEffect( dep: Dep, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { - if (!effect._trackToken) { - if (effect.scheduler || !_WeakRef) { - effect._trackToken = effect - } else { - effect._trackToken = new _WeakRef(effect) - registry?.register(effect, effect._trackToken, effect) - } - } - const trackToken = effect._trackToken! - if (dep.get(trackToken) !== effect._trackId) { - dep.set(trackToken, effect._trackId) - let deps = depsMap.get(trackToken) - if (!deps) { - depsMap.set(trackToken, (deps = [])) - } - const oldDep = deps[effect._depsLength] + 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) } - deps[effect._depsLength++] = dep + effect.deps[effect._depsLength++] = dep } else { effect._depsLength++ } From b93299c1155756bdfbb9d123bbc959ad9231eab2 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 26 Oct 2023 17:47:15 +0800 Subject: [PATCH 191/192] chore: remove `TrackToken` type --- packages/reactivity/src/dep.ts | 4 ++-- packages/reactivity/src/effect.ts | 12 +----------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/packages/reactivity/src/dep.ts b/packages/reactivity/src/dep.ts index ee83e116611..eafb2a8af3f 100644 --- a/packages/reactivity/src/dep.ts +++ b/packages/reactivity/src/dep.ts @@ -1,7 +1,7 @@ -import type { TrackToken } from './effect' +import type { ReactiveEffect } from './effect' import type { ComputedRefImpl } from './computed' -export type Dep = Map & { +export type Dep = Map & { cleanup: () => void computed?: ComputedRefImpl } diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 4d852615e06..3a25295011c 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -21,8 +21,6 @@ export type DebuggerEventExtraInfo = { export let activeEffect: ReactiveEffect | undefined -export type TrackToken = WeakRef | ReactiveEffect - export class ReactiveEffect { active = true deps: Dep[] = [] @@ -125,10 +123,6 @@ export class ReactiveEffect { this.active = false } } - - deref() { - return this - } } function triggerComputed(computed: ComputedRefImpl) { @@ -291,11 +285,7 @@ export function triggerEffects( debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { pauseScheduling() - for (const trackToken of dep.keys()) { - const effect = trackToken.deref() - if (!effect) { - continue - } + for (const effect of dep.keys()) { if (!effect.allowRecurse && effect._runnings) { continue } From 05558926b2fe033bb8881ae6467259e72f572867 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 26 Oct 2023 17:48:34 +0800 Subject: [PATCH 192/192] chore: revert `threads` option --- vitest.config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vitest.config.ts b/vitest.config.ts index a829c130725..e5d5f59345f 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -22,8 +22,8 @@ export default defineConfig({ }, test: { globals: true, - // threads must be disabled as FinalizationRegistry causes vitest zombie process somehow - threads: false, + // disable threads on GH actions to speed it up + threads: !process.env.GITHUB_ACTIONS, setupFiles: 'scripts/setupVitest.ts', environmentMatchGlobs: [ ['packages/{vue,vue-compat,runtime-dom}/**', 'jsdom']