]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(reactivity): effect shoud only recursively self trigger with explicit options
authorEvan You <yyx990803@gmail.com>
Wed, 16 Sep 2020 14:52:31 +0000 (10:52 -0400)
committerEvan You <yyx990803@gmail.com>
Wed, 16 Sep 2020 14:52:31 +0000 (10:52 -0400)
fix #2125

packages/reactivity/src/effect.ts
packages/runtime-core/__tests__/apiWatch.spec.ts
packages/runtime-core/__tests__/rendererComponent.spec.ts
packages/runtime-core/src/renderer.ts

index 495d6714939cdc81f8ccd2766bf2d37b97f71a1d..2d2b4d2a1bedd6d8af9d49349d639dea0893a4f2 100644 (file)
@@ -25,6 +25,7 @@ export interface ReactiveEffectOptions {
   onTrack?: (event: DebuggerEvent) => void
   onTrigger?: (event: DebuggerEvent) => void
   onStop?: () => void
+  allowRecurse?: boolean
 }
 
 export type DebuggerEvent = {
@@ -178,7 +179,11 @@ export function trigger(
   const effects = new Set<ReactiveEffect>()
   const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
     if (effectsToAdd) {
-      effectsToAdd.forEach(effect => effects.add(effect))
+      effectsToAdd.forEach(effect => {
+        if (effect !== activeEffect || effect.options.allowRecurse) {
+          effects.add(effect)
+        }
+      })
     }
   }
 
index 138a3e04f5d0b57a9b13163a310e992974123fac..7b64e50bd5f578630a6d4f5665cb4b8472a70774 100644 (file)
@@ -779,4 +779,17 @@ describe('api: watch', () => {
     // should trigger now
     expect(sideEffect).toBe(2)
   })
+
+  // #2125
+  test('watchEffect should not recursively trigger itself', async () => {
+    const spy = jest.fn()
+    const price = ref(10)
+    const history = ref<number[]>([])
+    watchEffect(() => {
+      history.value.push(price.value)
+      spy()
+    })
+    await nextTick()
+    expect(spy).toHaveBeenCalledTimes(1)
+  })
 })
index 243e4cbd0bea95eebe1ae6abc71c829640634243..4253779d4cfe3a6ee079899b87b49d98ac7866bb 100644 (file)
@@ -5,7 +5,10 @@ import {
   nodeOps,
   serializeInner,
   nextTick,
-  VNode
+  VNode,
+  provide,
+  inject,
+  Ref
 } from '@vue/runtime-test'
 
 describe('renderer: component', () => {
@@ -104,4 +107,34 @@ describe('renderer: component', () => {
     )
     expect(Comp1.updated).not.toHaveBeenCalled()
   })
+
+  // #2043
+  test('component child synchronously updating parent state should trigger parent re-render', async () => {
+    const App = {
+      setup() {
+        const n = ref(0)
+        provide('foo', n)
+        return () => {
+          return [h('div', n.value), h(Child)]
+        }
+      }
+    }
+
+    const Child = {
+      setup() {
+        const n = inject<Ref<number>>('foo')!
+        n.value++
+
+        return () => {
+          return h('div', n.value)
+        }
+      }
+    }
+
+    const root = nodeOps.createElement('div')
+    render(h(App), root)
+    expect(serializeInner(root)).toBe(`<div>0</div><div>1</div>`)
+    await nextTick()
+    expect(serializeInner(root)).toBe(`<div>1</div><div>1</div>`)
+  })
 })
index f38fc9da13497c4ca90c758ea6805301af1ff014..485dcf15077934ea903cd9c4a1dbc96d2990b777 100644 (file)
@@ -44,7 +44,6 @@ import {
   flushPostFlushCbs,
   invalidateJob,
   flushPreFlushCbs,
-  SchedulerJob,
   SchedulerCb
 } from './scheduler'
 import { effect, stop, ReactiveEffectOptions, isRef } from '@vue/reactivity'
@@ -261,7 +260,9 @@ export const enum MoveType {
 }
 
 const prodEffectOptions = {
-  scheduler: queueJob
+  scheduler: queueJob,
+  // #1801, #2043 component render effects should allow recursive updates
+  allowRecurse: true
 }
 
 function createDevEffectOptions(
@@ -269,6 +270,7 @@ function createDevEffectOptions(
 ): ReactiveEffectOptions {
   return {
     scheduler: queueJob,
+    allowRecurse: true,
     onTrack: instance.rtc ? e => invokeArrayFns(instance.rtc!, e) : void 0,
     onTrigger: instance.rtg ? e => invokeArrayFns(instance.rtg!, e) : void 0
   }
@@ -1489,8 +1491,6 @@ function baseCreateRenderer(
         }
       }
     }, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
-    // #1801 mark it to allow recursive updates
-    ;(instance.update as SchedulerJob).allowRecurse = true
   }
 
   const updateComponentPreRender = (