Skip to content

Commit 3810de7

Browse files
committed
fix(reactivity): effect shoud only recursively self trigger with explicit options
fix #2125
1 parent 89e9ab8 commit 3810de7

File tree

4 files changed

+57
-6
lines changed

4 files changed

+57
-6
lines changed

packages/reactivity/src/effect.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export interface ReactiveEffectOptions {
2525
onTrack?: (event: DebuggerEvent) => void
2626
onTrigger?: (event: DebuggerEvent) => void
2727
onStop?: () => void
28+
allowRecurse?: boolean
2829
}
2930

3031
export type DebuggerEvent = {
@@ -178,7 +179,11 @@ export function trigger(
178179
const effects = new Set<ReactiveEffect>()
179180
const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
180181
if (effectsToAdd) {
181-
effectsToAdd.forEach(effect => effects.add(effect))
182+
effectsToAdd.forEach(effect => {
183+
if (effect !== activeEffect || effect.options.allowRecurse) {
184+
effects.add(effect)
185+
}
186+
})
182187
}
183188
}
184189

packages/runtime-core/__tests__/apiWatch.spec.ts

+13
Original file line numberDiff line numberDiff line change
@@ -779,4 +779,17 @@ describe('api: watch', () => {
779779
// should trigger now
780780
expect(sideEffect).toBe(2)
781781
})
782+
783+
// #2125
784+
test('watchEffect should not recursively trigger itself', async () => {
785+
const spy = jest.fn()
786+
const price = ref(10)
787+
const history = ref<number[]>([])
788+
watchEffect(() => {
789+
history.value.push(price.value)
790+
spy()
791+
})
792+
await nextTick()
793+
expect(spy).toHaveBeenCalledTimes(1)
794+
})
782795
})

packages/runtime-core/__tests__/rendererComponent.spec.ts

+34-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import {
55
nodeOps,
66
serializeInner,
77
nextTick,
8-
VNode
8+
VNode,
9+
provide,
10+
inject,
11+
Ref
912
} from '@vue/runtime-test'
1013

1114
describe('renderer: component', () => {
@@ -104,4 +107,34 @@ describe('renderer: component', () => {
104107
)
105108
expect(Comp1.updated).not.toHaveBeenCalled()
106109
})
110+
111+
// #2043
112+
test('component child synchronously updating parent state should trigger parent re-render', async () => {
113+
const App = {
114+
setup() {
115+
const n = ref(0)
116+
provide('foo', n)
117+
return () => {
118+
return [h('div', n.value), h(Child)]
119+
}
120+
}
121+
}
122+
123+
const Child = {
124+
setup() {
125+
const n = inject<Ref<number>>('foo')!
126+
n.value++
127+
128+
return () => {
129+
return h('div', n.value)
130+
}
131+
}
132+
}
133+
134+
const root = nodeOps.createElement('div')
135+
render(h(App), root)
136+
expect(serializeInner(root)).toBe(`<div>0</div><div>1</div>`)
137+
await nextTick()
138+
expect(serializeInner(root)).toBe(`<div>1</div><div>1</div>`)
139+
})
107140
})

packages/runtime-core/src/renderer.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ import {
4444
flushPostFlushCbs,
4545
invalidateJob,
4646
flushPreFlushCbs,
47-
SchedulerJob,
4847
SchedulerCb
4948
} from './scheduler'
5049
import { effect, stop, ReactiveEffectOptions, isRef } from '@vue/reactivity'
@@ -261,14 +260,17 @@ export const enum MoveType {
261260
}
262261

263262
const prodEffectOptions = {
264-
scheduler: queueJob
263+
scheduler: queueJob,
264+
// #1801, #2043 component render effects should allow recursive updates
265+
allowRecurse: true
265266
}
266267

267268
function createDevEffectOptions(
268269
instance: ComponentInternalInstance
269270
): ReactiveEffectOptions {
270271
return {
271272
scheduler: queueJob,
273+
allowRecurse: true,
272274
onTrack: instance.rtc ? e => invokeArrayFns(instance.rtc!, e) : void 0,
273275
onTrigger: instance.rtg ? e => invokeArrayFns(instance.rtg!, e) : void 0
274276
}
@@ -1489,8 +1491,6 @@ function baseCreateRenderer(
14891491
}
14901492
}
14911493
}, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
1492-
// #1801 mark it to allow recursive updates
1493-
;(instance.update as SchedulerJob).allowRecurse = true
14941494
}
14951495

14961496
const updateComponentPreRender = (

0 commit comments

Comments
 (0)