expect(fnSpy).toBeCalledTimes(2)
})
+ it('should mark dirty as MaybeDirty_ComputedSideEffect_Origin', () => {
+ const v = ref(1)
+ const c = computed(() => {
+ v.value += 1
+ return v.value
+ })
+
+ c.value
+ expect(c.effect._dirtyLevel).toBe(
+ DirtyLevels.MaybeDirty_ComputedSideEffect_Origin,
+ )
+ expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
+ })
+
+ it('should not infinite re-run effect when effect access original side effect computed', async () => {
+ const spy = vi.fn()
+ const v = ref(0)
+ const c = computed(() => {
+ v.value += 1
+ return v.value
+ })
+ const Comp = {
+ setup: () => {
+ return () => {
+ spy()
+ return v.value + c.value
+ }
+ },
+ }
+ const root = nodeOps.createElement('div')
+
+ render(h(Comp), root)
+ expect(spy).toBeCalledTimes(1)
+ await nextTick()
+ expect(c.effect._dirtyLevel).toBe(
+ DirtyLevels.MaybeDirty_ComputedSideEffect_Origin,
+ )
+ expect(serializeInner(root)).toBe('2')
+ expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
+ })
+
+ it('should not infinite re-run effect when effect access chained side effect computed', async () => {
+ const spy = vi.fn()
+ const v = ref(0)
+ const c1 = computed(() => {
+ v.value += 1
+ return v.value
+ })
+ const c2 = computed(() => v.value + c1.value)
+ const Comp = {
+ setup: () => {
+ return () => {
+ spy()
+ return v.value + c1.value + c2.value
+ }
+ },
+ }
+ const root = nodeOps.createElement('div')
+
+ render(h(Comp), root)
+ expect(spy).toBeCalledTimes(1)
+ await nextTick()
+ expect(c1.effect._dirtyLevel).toBe(
+ DirtyLevels.MaybeDirty_ComputedSideEffect_Origin,
+ )
+ expect(c2.effect._dirtyLevel).toBe(
+ DirtyLevels.MaybeDirty_ComputedSideEffect,
+ )
+ expect(serializeInner(root)).toBe('4')
+ expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
+ })
+
it('should chained recurse effects clear dirty after trigger', () => {
const v = ref(1)
const c1 = computed(() => v.value)
c3.value
- expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
+ expect(c1.effect._dirtyLevel).toBe(
+ DirtyLevels.MaybeDirty_ComputedSideEffect_Origin,
+ )
expect(c2.effect._dirtyLevel).toBe(
DirtyLevels.MaybeDirty_ComputedSideEffect,
)
})
const c2 = computed(() => v.value + c1.value)
expect(c2.value).toBe('0foo')
- expect(c2.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
+ expect(c2.effect._dirtyLevel).toBe(
+ DirtyLevels.MaybeDirty_ComputedSideEffect,
+ )
expect(c2.value).toBe('1foo')
expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
})
c2.value
})
expect(fnSpy).toBeCalledTimes(1)
- expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
- expect(c2.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
+ expect(c1.effect._dirtyLevel).toBe(
+ DirtyLevels.MaybeDirty_ComputedSideEffect_Origin,
+ )
+ expect(c2.effect._dirtyLevel).toBe(
+ DirtyLevels.MaybeDirty_ComputedSideEffect,
+ )
v.value = 2
expect(fnSpy).toBeCalledTimes(2)
expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
expect(c3.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
c3.value
- expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
+ expect(c1.effect._dirtyLevel).toBe(
+ DirtyLevels.MaybeDirty_ComputedSideEffect_Origin,
+ )
expect(c2.effect._dirtyLevel).toBe(
DirtyLevels.MaybeDirty_ComputedSideEffect,
)
render(h(Comp), root)
await nextTick()
+ expect(c.effect._dirtyLevel).toBe(
+ DirtyLevels.MaybeDirty_ComputedSideEffect_Origin,
+ )
expect(serializeInner(root)).toBe('Hello World')
v.value += ' World'
+ expect(c.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
await nextTick()
- expect(serializeInner(root)).toBe('Hello World World World World')
+ expect(c.effect._dirtyLevel).toBe(
+ DirtyLevels.MaybeDirty_ComputedSideEffect_Origin,
+ )
+ expect(serializeInner(root)).toBe('Hello World World World')
expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
})
}
public get dirty() {
+ // treat original side effect computed as not dirty to avoid infinite loop
+ if (this._dirtyLevel === DirtyLevels.MaybeDirty_ComputedSideEffect_Origin)
+ return false
if (
this._dirtyLevel === DirtyLevels.MaybeDirty_ComputedSideEffect ||
this._dirtyLevel === DirtyLevels.MaybeDirty
for (let i = 0; i < this._depsLength; i++) {
const dep = this.deps[i]
if (dep.computed) {
+ // treat chained side effect computed as dirty to force it re-run
+ // since we know the original side effect computed is dirty
+ if (
+ dep.computed.effect._dirtyLevel ===
+ DirtyLevels.MaybeDirty_ComputedSideEffect_Origin
+ )
+ return true
triggerComputed(dep.computed)
if (this._dirtyLevel >= DirtyLevels.Dirty) {
break
) {
pauseScheduling()
for (const effect of dep.keys()) {
+ if (!dep.computed && effect.computed) {
+ if (dep.get(effect) === effect._trackId && effect._runnings > 0) {
+ effect._dirtyLevel = DirtyLevels.MaybeDirty_ComputedSideEffect_Origin
+ continue
+ }
+ }
// dep.get(effect) is very expensive, we need to calculate it lazily and reuse the result
let tracking: boolean | undefined
if (
(tracking ??= dep.get(effect) === effect._trackId)
) {
effect._shouldSchedule ||= effect._dirtyLevel === DirtyLevels.NotDirty
+ // always schedule if the computed is original side effect
+ // since we know it is actually dirty
+ if (
+ effect.computed &&
+ effect._dirtyLevel === DirtyLevels.MaybeDirty_ComputedSideEffect_Origin
+ ) {
+ effect._shouldSchedule = true
+ }
effect._dirtyLevel = dirtyLevel
}
if (