Skip to content

Commit e45a8d2

Browse files
fix(reactivity): fix dirtyLevel checks for recursive effects (#10101)
close #10082
1 parent ffd0473 commit e45a8d2

File tree

5 files changed

+95
-26
lines changed

5 files changed

+95
-26
lines changed

packages/reactivity/__tests__/computed.spec.ts

+30
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
ref,
1212
toRaw,
1313
} from '../src'
14+
import { DirtyLevels } from '../src/constants'
1415

1516
describe('reactivity/computed', () => {
1617
it('should return updated value', () => {
@@ -451,4 +452,33 @@ describe('reactivity/computed', () => {
451452
v.value = 2
452453
expect(fnSpy).toBeCalledTimes(2)
453454
})
455+
456+
it('...', () => {
457+
const fnSpy = vi.fn()
458+
const v = ref(1)
459+
const c1 = computed(() => v.value)
460+
const c2 = computed(() => {
461+
fnSpy()
462+
return c1.value
463+
})
464+
465+
c1.effect.allowRecurse = true
466+
c2.effect.allowRecurse = true
467+
c2.value
468+
469+
expect(c1.effect._dirtyLevel).toBe(DirtyLevels.NotDirty)
470+
expect(c2.effect._dirtyLevel).toBe(DirtyLevels.NotDirty)
471+
})
472+
473+
it('should work when chained(ref+computed)', () => {
474+
const value = ref(0)
475+
const consumer = computed(() => {
476+
value.value++
477+
return 'foo'
478+
})
479+
const provider = computed(() => value.value + consumer.value)
480+
expect(provider.value).toBe('0foo')
481+
expect(provider.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
482+
expect(provider.value).toBe('1foo')
483+
})
454484
})

packages/reactivity/__tests__/effect.spec.ts

+38
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ import {
1313
} from '../src/index'
1414
import { pauseScheduling, resetScheduling } from '../src/effect'
1515
import { ITERATE_KEY, getDepFromReactive } from '../src/reactiveEffect'
16+
import {
17+
computed,
18+
h,
19+
nextTick,
20+
nodeOps,
21+
ref,
22+
render,
23+
serializeInner,
24+
} from '@vue/runtime-test'
1625

1726
describe('reactivity/effect', () => {
1827
it('should run the passed function once (wrapped by a effect)', () => {
@@ -1011,6 +1020,35 @@ describe('reactivity/effect', () => {
10111020
expect(counterSpy).toHaveBeenCalledTimes(1)
10121021
})
10131022

1023+
// #10082
1024+
it('should set dirtyLevel when effect is allowRecurse and is running', async () => {
1025+
const s = ref(0)
1026+
const n = computed(() => s.value + 1)
1027+
1028+
const Child = {
1029+
setup() {
1030+
s.value++
1031+
return () => n.value
1032+
},
1033+
}
1034+
1035+
const renderSpy = vi.fn()
1036+
const Parent = {
1037+
setup() {
1038+
return () => {
1039+
renderSpy()
1040+
return [n.value, h(Child)]
1041+
}
1042+
},
1043+
}
1044+
1045+
const root = nodeOps.createElement('div')
1046+
render(h(Parent), root)
1047+
await nextTick()
1048+
expect(serializeInner(root)).toBe('22')
1049+
expect(renderSpy).toHaveBeenCalledTimes(2)
1050+
})
1051+
10141052
describe('empty dep cleanup', () => {
10151053
it('should remove the dep when the effect is stopped', () => {
10161054
const obj = reactive({ prop: 1 })

packages/reactivity/src/computed.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export class ComputedRefImpl<T> {
4343
) {
4444
this.effect = new ReactiveEffect(
4545
() => getter(this._value),
46-
() => triggerRefValue(this, DirtyLevels.ComputedValueMaybeDirty),
46+
() => triggerRefValue(this, DirtyLevels.MaybeDirty),
4747
)
4848
this.effect.computed = this
4949
this.effect.active = this._cacheable = !isSSR
@@ -53,12 +53,12 @@ export class ComputedRefImpl<T> {
5353
get value() {
5454
// the computed ref may get wrapped by other proxies e.g. readonly() #3376
5555
const self = toRaw(this)
56-
trackRefValue(self)
5756
if (!self._cacheable || self.effect.dirty) {
5857
if (hasChanged(self._value, (self._value = self.effect.run()!))) {
59-
triggerRefValue(self, DirtyLevels.ComputedValueDirty)
58+
triggerRefValue(self, DirtyLevels.Dirty)
6059
}
6160
}
61+
trackRefValue(self)
6262
return self._value
6363
}
6464

packages/reactivity/src/constants.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ export enum ReactiveFlags {
2424

2525
export enum DirtyLevels {
2626
NotDirty = 0,
27-
ComputedValueMaybeDirty = 1,
28-
ComputedValueDirty = 2,
29-
Dirty = 3,
27+
MaybeDirty = 1,
28+
Dirty = 2,
3029
}

packages/reactivity/src/effect.ts

+22-20
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export class ReactiveEffect<T = any> {
6060
/**
6161
* @internal
6262
*/
63-
_queryings = 0
63+
_shouldSchedule = false
6464
/**
6565
* @internal
6666
*/
@@ -76,22 +76,23 @@ export class ReactiveEffect<T = any> {
7676
}
7777

7878
public get dirty() {
79-
if (this._dirtyLevel === DirtyLevels.ComputedValueMaybeDirty) {
80-
this._dirtyLevel = DirtyLevels.NotDirty
81-
this._queryings++
79+
if (this._dirtyLevel === DirtyLevels.MaybeDirty) {
8280
pauseTracking()
83-
for (const dep of this.deps) {
81+
for (let i = 0; i < this._depsLength; i++) {
82+
const dep = this.deps[i]
8483
if (dep.computed) {
8584
triggerComputed(dep.computed)
86-
if (this._dirtyLevel >= DirtyLevels.ComputedValueDirty) {
85+
if (this._dirtyLevel >= DirtyLevels.Dirty) {
8786
break
8887
}
8988
}
9089
}
90+
if (this._dirtyLevel < DirtyLevels.Dirty) {
91+
this._dirtyLevel = DirtyLevels.NotDirty
92+
}
9193
resetTracking()
92-
this._queryings--
9394
}
94-
return this._dirtyLevel >= DirtyLevels.ComputedValueDirty
95+
return this._dirtyLevel >= DirtyLevels.Dirty
9596
}
9697

9798
public set dirty(v) {
@@ -290,28 +291,29 @@ export function triggerEffects(
290291
) {
291292
pauseScheduling()
292293
for (const effect of dep.keys()) {
293-
if (!effect.allowRecurse && effect._runnings) {
294+
if (dep.get(effect) !== effect._trackId) {
295+
// when recurse effect is running, dep map could have outdated items
294296
continue
295297
}
296-
if (
297-
effect._dirtyLevel < dirtyLevel &&
298-
(!effect._runnings || dirtyLevel !== DirtyLevels.ComputedValueDirty)
299-
) {
298+
if (effect._dirtyLevel < dirtyLevel) {
300299
const lastDirtyLevel = effect._dirtyLevel
301300
effect._dirtyLevel = dirtyLevel
302-
if (
303-
lastDirtyLevel === DirtyLevels.NotDirty &&
304-
(!effect._queryings || dirtyLevel !== DirtyLevels.ComputedValueDirty)
305-
) {
301+
if (lastDirtyLevel === DirtyLevels.NotDirty) {
302+
effect._shouldSchedule = true
306303
if (__DEV__) {
307304
effect.onTrigger?.(extend({ effect }, debuggerEventExtraInfo))
308305
}
309306
effect.trigger()
310-
if (effect.scheduler) {
311-
queueEffectSchedulers.push(effect.scheduler)
312-
}
313307
}
314308
}
309+
if (
310+
effect.scheduler &&
311+
effect._shouldSchedule &&
312+
(!effect._runnings || effect.allowRecurse)
313+
) {
314+
effect._shouldSchedule = false
315+
queueEffectSchedulers.push(effect.scheduler)
316+
}
315317
}
316318
resetScheduling()
317319
}

0 commit comments

Comments
 (0)