Skip to content

Commit c2b274a

Browse files
fix(reactivity): re-fix #10114 (#10123)
1 parent e58d3f6 commit c2b274a

File tree

3 files changed

+72
-14
lines changed

3 files changed

+72
-14
lines changed

packages/reactivity/__tests__/computed.spec.ts

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { h, nextTick, nodeOps, render, serializeInner } from '@vue/runtime-test'
22
import {
33
type DebuggerEvent,
44
ITERATE_KEY,
5+
ReactiveEffect,
56
TrackOpTypes,
67
TriggerOpTypes,
78
type WritableComputedRef,
@@ -454,14 +455,10 @@ describe('reactivity/computed', () => {
454455
expect(fnSpy).toBeCalledTimes(2)
455456
})
456457

457-
it('...', () => {
458-
const fnSpy = vi.fn()
458+
it('should chained recurse effects clear dirty after trigger', () => {
459459
const v = ref(1)
460460
const c1 = computed(() => v.value)
461-
const c2 = computed(() => {
462-
fnSpy()
463-
return c1.value
464-
})
461+
const c2 = computed(() => c1.value)
465462

466463
c1.effect.allowRecurse = true
467464
c2.effect.allowRecurse = true
@@ -471,6 +468,60 @@ describe('reactivity/computed', () => {
471468
expect(c2.effect._dirtyLevel).toBe(DirtyLevels.NotDirty)
472469
})
473470

471+
it('should chained computeds dirtyLevel update with first computed effect', () => {
472+
const v = ref(0)
473+
const c1 = computed(() => {
474+
if (v.value === 0) {
475+
v.value = 1
476+
}
477+
return v.value
478+
})
479+
const c2 = computed(() => c1.value)
480+
const c3 = computed(() => c2.value)
481+
482+
c3.value
483+
484+
expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
485+
expect(c2.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
486+
expect(c3.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
487+
})
488+
489+
it('should work when chained(ref+computed)', () => {
490+
const v = ref(0)
491+
const c1 = computed(() => {
492+
if (v.value === 0) {
493+
v.value = 1
494+
}
495+
return 'foo'
496+
})
497+
const c2 = computed(() => v.value + c1.value)
498+
expect(c2.value).toBe('0foo')
499+
expect(c2.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
500+
expect(c2.value).toBe('1foo')
501+
})
502+
503+
it('should trigger effect even computed already dirty', () => {
504+
const fnSpy = vi.fn()
505+
const v = ref(0)
506+
const c1 = computed(() => {
507+
if (v.value === 0) {
508+
v.value = 1
509+
}
510+
return 'foo'
511+
})
512+
const c2 = computed(() => v.value + c1.value)
513+
514+
effect(() => {
515+
fnSpy()
516+
c2.value
517+
})
518+
expect(fnSpy).toBeCalledTimes(1)
519+
expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
520+
expect(c2.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
521+
v.value = 2
522+
expect(fnSpy).toBeCalledTimes(2)
523+
})
524+
474525
it('should be not dirty after deps mutate (mutate deps in computed)', async () => {
475526
const state = reactive<any>({})
476527
const consumer = computed(() => {

packages/reactivity/src/computed.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type DebuggerOptions, ReactiveEffect } from './effect'
1+
import { type DebuggerOptions, ReactiveEffect, scheduleEffects } from './effect'
22
import { type Ref, trackRefValue, triggerRefValue } from './ref'
33
import { NOOP, hasChanged, isFunction } from '@vue/shared'
44
import { toRaw } from './reactive'
@@ -44,6 +44,7 @@ export class ComputedRefImpl<T> {
4444
this.effect = new ReactiveEffect(
4545
() => getter(this._value),
4646
() => triggerRefValue(this, DirtyLevels.MaybeDirty),
47+
() => this.dep && scheduleEffects(this.dep),
4748
)
4849
this.effect.computed = this
4950
this.effect.active = this._cacheable = !isSSR
@@ -59,6 +60,9 @@ export class ComputedRefImpl<T> {
5960
}
6061
}
6162
trackRefValue(self)
63+
if (self.effect._dirtyLevel >= DirtyLevels.MaybeDirty) {
64+
triggerRefValue(self, DirtyLevels.MaybeDirty)
65+
}
6266
return self._value
6367
}
6468

packages/reactivity/src/effect.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -291,13 +291,9 @@ export function triggerEffects(
291291
) {
292292
pauseScheduling()
293293
for (const effect of dep.keys()) {
294-
if (dep.get(effect) !== effect._trackId) {
295-
// when recurse effect is running, dep map could have outdated items
296-
continue
297-
}
298294
if (
299295
effect._dirtyLevel < dirtyLevel &&
300-
!(effect._runnings && !effect.allowRecurse)
296+
dep.get(effect) === effect._trackId
301297
) {
302298
const lastDirtyLevel = effect._dirtyLevel
303299
effect._dirtyLevel = dirtyLevel
@@ -309,14 +305,21 @@ export function triggerEffects(
309305
effect.trigger()
310306
}
311307
}
308+
}
309+
scheduleEffects(dep)
310+
resetScheduling()
311+
}
312+
313+
export function scheduleEffects(dep: Dep) {
314+
for (const effect of dep.keys()) {
312315
if (
313316
effect.scheduler &&
314317
effect._shouldSchedule &&
315-
(!effect._runnings || effect.allowRecurse)
318+
(!effect._runnings || effect.allowRecurse) &&
319+
dep.get(effect) === effect._trackId
316320
) {
317321
effect._shouldSchedule = false
318322
queueEffectSchedulers.push(effect.scheduler)
319323
}
320324
}
321-
resetScheduling()
322325
}

0 commit comments

Comments
 (0)