import {
type DebuggerEvent,
ITERATE_KEY,
+ ReactiveEffect,
TrackOpTypes,
TriggerOpTypes,
type WritableComputedRef,
expect(fnSpy).toBeCalledTimes(2)
})
- it('...', () => {
- const fnSpy = vi.fn()
+ it('should chained recurse effects clear dirty after trigger', () => {
const v = ref(1)
const c1 = computed(() => v.value)
- const c2 = computed(() => {
- fnSpy()
- return c1.value
- })
+ const c2 = computed(() => c1.value)
c1.effect.allowRecurse = true
c2.effect.allowRecurse = true
expect(c2.effect._dirtyLevel).toBe(DirtyLevels.NotDirty)
})
+ it('should chained computeds dirtyLevel update with first computed effect', () => {
+ const v = ref(0)
+ const c1 = computed(() => {
+ if (v.value === 0) {
+ v.value = 1
+ }
+ return v.value
+ })
+ const c2 = computed(() => c1.value)
+ const c3 = computed(() => c2.value)
+
+ c3.value
+
+ expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
+ expect(c2.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
+ expect(c3.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
+ })
+
+ it('should work when chained(ref+computed)', () => {
+ const v = ref(0)
+ const c1 = computed(() => {
+ if (v.value === 0) {
+ v.value = 1
+ }
+ return 'foo'
+ })
+ const c2 = computed(() => v.value + c1.value)
+ expect(c2.value).toBe('0foo')
+ expect(c2.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
+ expect(c2.value).toBe('1foo')
+ })
+
+ it('should trigger effect even computed already dirty', () => {
+ const fnSpy = vi.fn()
+ const v = ref(0)
+ const c1 = computed(() => {
+ if (v.value === 0) {
+ v.value = 1
+ }
+ return 'foo'
+ })
+ const c2 = computed(() => v.value + c1.value)
+
+ effect(() => {
+ fnSpy()
+ c2.value
+ })
+ expect(fnSpy).toBeCalledTimes(1)
+ expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
+ expect(c2.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
+ v.value = 2
+ expect(fnSpy).toBeCalledTimes(2)
+ })
+
it('should be not dirty after deps mutate (mutate deps in computed)', async () => {
const state = reactive<any>({})
const consumer = computed(() => {
-import { type DebuggerOptions, ReactiveEffect } from './effect'
+import { type DebuggerOptions, ReactiveEffect, scheduleEffects } from './effect'
import { type Ref, trackRefValue, triggerRefValue } from './ref'
import { NOOP, hasChanged, isFunction } from '@vue/shared'
import { toRaw } from './reactive'
this.effect = new ReactiveEffect(
() => getter(this._value),
() => triggerRefValue(this, DirtyLevels.MaybeDirty),
+ () => this.dep && scheduleEffects(this.dep),
)
this.effect.computed = this
this.effect.active = this._cacheable = !isSSR
}
}
trackRefValue(self)
+ if (self.effect._dirtyLevel >= DirtyLevels.MaybeDirty) {
+ triggerRefValue(self, DirtyLevels.MaybeDirty)
+ }
return self._value
}
) {
pauseScheduling()
for (const effect of dep.keys()) {
- if (dep.get(effect) !== effect._trackId) {
- // when recurse effect is running, dep map could have outdated items
- continue
- }
if (
effect._dirtyLevel < dirtyLevel &&
- !(effect._runnings && !effect.allowRecurse)
+ dep.get(effect) === effect._trackId
) {
const lastDirtyLevel = effect._dirtyLevel
effect._dirtyLevel = dirtyLevel
effect.trigger()
}
}
+ }
+ scheduleEffects(dep)
+ resetScheduling()
+}
+
+export function scheduleEffects(dep: Dep) {
+ for (const effect of dep.keys()) {
if (
effect.scheduler &&
effect._shouldSchedule &&
- (!effect._runnings || effect.allowRecurse)
+ (!effect._runnings || effect.allowRecurse) &&
+ dep.get(effect) === effect._trackId
) {
effect._shouldSchedule = false
queueEffectSchedulers.push(effect.scheduler)
}
}
- resetScheduling()
}