Skip to content

Commit 6c7e0bd

Browse files
authoredFeb 6, 2024··
fix(reactivity): handle MaybeDirty recurse (#10187)
close #10185
1 parent 91f058a commit 6c7e0bd

File tree

4 files changed

+68
-31
lines changed

4 files changed

+68
-31
lines changed
 

‎packages/reactivity/__tests__/computed.spec.ts

+40
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
isReadonly,
1111
reactive,
1212
ref,
13+
shallowRef,
1314
toRaw,
1415
} from '../src'
1516
import { DirtyLevels } from '../src/constants'
@@ -521,6 +522,45 @@ describe('reactivity/computed', () => {
521522
expect(fnSpy).toBeCalledTimes(2)
522523
})
523524

525+
// #10185
526+
it('should not override queried MaybeDirty result', () => {
527+
class Item {
528+
v = ref(0)
529+
}
530+
const v1 = shallowRef()
531+
const v2 = ref(false)
532+
const c1 = computed(() => {
533+
let c = v1.value
534+
if (!v1.value) {
535+
c = new Item()
536+
v1.value = c
537+
}
538+
return c.v.value
539+
})
540+
const c2 = computed(() => {
541+
if (!v2.value) return 'no'
542+
return c1.value ? 'yes' : 'no'
543+
})
544+
const c3 = computed(() => c2.value)
545+
546+
c3.value
547+
v2.value = true
548+
expect(c2.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
549+
expect(c3.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
550+
551+
c3.value
552+
expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
553+
expect(c2.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
554+
expect(c3.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
555+
556+
v1.value.v.value = 999
557+
expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
558+
expect(c2.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
559+
expect(c3.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
560+
561+
expect(c3.value).toBe('yes')
562+
})
563+
524564
it('should be not dirty after deps mutate (mutate deps in computed)', async () => {
525565
const state = reactive<any>({})
526566
const consumer = computed(() => {

‎packages/reactivity/src/computed.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type DebuggerOptions, ReactiveEffect, scheduleEffects } from './effect'
1+
import { type DebuggerOptions, ReactiveEffect } from './effect'
22
import { type Ref, trackRefValue, triggerRefValue } from './ref'
33
import { NOOP, hasChanged, isFunction } from '@vue/shared'
44
import { toRaw } from './reactive'
@@ -44,7 +44,6 @@ export class ComputedRefImpl<T> {
4444
this.effect = new ReactiveEffect(
4545
() => getter(this._value),
4646
() => triggerRefValue(this, DirtyLevels.MaybeDirty),
47-
() => this.dep && scheduleEffects(this.dep),
4847
)
4948
this.effect.computed = this
5049
this.effect.active = this._cacheable = !isSSR
@@ -54,10 +53,11 @@ export class ComputedRefImpl<T> {
5453
get value() {
5554
// the computed ref may get wrapped by other proxies e.g. readonly() #3376
5655
const self = toRaw(this)
57-
if (!self._cacheable || self.effect.dirty) {
58-
if (hasChanged(self._value, (self._value = self.effect.run()!))) {
59-
triggerRefValue(self, DirtyLevels.Dirty)
60-
}
56+
if (
57+
(!self._cacheable || self.effect.dirty) &&
58+
hasChanged(self._value, (self._value = self.effect.run()!))
59+
) {
60+
triggerRefValue(self, DirtyLevels.Dirty)
6161
}
6262
trackRefValue(self)
6363
if (self.effect._dirtyLevel >= DirtyLevels.MaybeDirty) {

‎packages/reactivity/src/constants.ts

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

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

‎packages/reactivity/src/effect.ts

+19-23
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export class ReactiveEffect<T = any> {
7777

7878
public get dirty() {
7979
if (this._dirtyLevel === DirtyLevels.MaybeDirty) {
80+
this._dirtyLevel = DirtyLevels.QueryingDirty
8081
pauseTracking()
8182
for (let i = 0; i < this._depsLength; i++) {
8283
const dep = this.deps[i]
@@ -87,7 +88,7 @@ export class ReactiveEffect<T = any> {
8788
}
8889
}
8990
}
90-
if (this._dirtyLevel < DirtyLevels.Dirty) {
91+
if (this._dirtyLevel === DirtyLevels.QueryingDirty) {
9192
this._dirtyLevel = DirtyLevels.NotDirty
9293
}
9394
resetTracking()
@@ -140,7 +141,7 @@ function preCleanupEffect(effect: ReactiveEffect) {
140141
}
141142

142143
function postCleanupEffect(effect: ReactiveEffect) {
143-
if (effect.deps && effect.deps.length > effect._depsLength) {
144+
if (effect.deps.length > effect._depsLength) {
144145
for (let i = effect._depsLength; i < effect.deps.length; i++) {
145146
cleanupDepEffect(effect.deps[i], effect)
146147
}
@@ -291,35 +292,30 @@ export function triggerEffects(
291292
) {
292293
pauseScheduling()
293294
for (const effect of dep.keys()) {
295+
// dep.get(effect) is very expensive, we need to calculate it lazily and reuse the result
296+
let tracking: boolean | undefined
294297
if (
295298
effect._dirtyLevel < dirtyLevel &&
296-
dep.get(effect) === effect._trackId
299+
(tracking ??= dep.get(effect) === effect._trackId)
297300
) {
298-
const lastDirtyLevel = effect._dirtyLevel
301+
effect._shouldSchedule ||= effect._dirtyLevel === DirtyLevels.NotDirty
299302
effect._dirtyLevel = dirtyLevel
300-
if (lastDirtyLevel === DirtyLevels.NotDirty) {
301-
effect._shouldSchedule = true
302-
if (__DEV__) {
303-
effect.onTrigger?.(extend({ effect }, debuggerEventExtraInfo))
304-
}
305-
effect.trigger()
306-
}
307303
}
308-
}
309-
scheduleEffects(dep)
310-
resetScheduling()
311-
}
312-
313-
export function scheduleEffects(dep: Dep) {
314-
for (const effect of dep.keys()) {
315304
if (
316-
effect.scheduler &&
317305
effect._shouldSchedule &&
318-
(!effect._runnings || effect.allowRecurse) &&
319-
dep.get(effect) === effect._trackId
306+
(tracking ??= dep.get(effect) === effect._trackId)
320307
) {
321-
effect._shouldSchedule = false
322-
queueEffectSchedulers.push(effect.scheduler)
308+
if (__DEV__) {
309+
effect.onTrigger?.(extend({ effect }, debuggerEventExtraInfo))
310+
}
311+
effect.trigger()
312+
if (!effect._runnings || effect.allowRecurse) {
313+
effect._shouldSchedule = false
314+
if (effect.scheduler) {
315+
queueEffectSchedulers.push(effect.scheduler)
316+
}
317+
}
323318
}
324319
}
320+
resetScheduling()
325321
}

0 commit comments

Comments
 (0)
Please sign in to comment.