]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(watch): exhaust pre-flush watchers + avoid duplicate render by pre-flush watchers
authorEvan You <yyx990803@gmail.com>
Tue, 4 Aug 2020 17:20:09 +0000 (13:20 -0400)
committerEvan You <yyx990803@gmail.com>
Tue, 4 Aug 2020 17:20:23 +0000 (13:20 -0400)
close #1777

packages/runtime-core/__tests__/apiWatch.spec.ts
packages/runtime-core/src/renderer.ts
packages/runtime-core/src/scheduler.ts

index e6172e7f5680d3a6fff7a7e2bbd5d6e81ced45d5..97dd131e812788abe50b74856123bc07c47f749a 100644 (file)
@@ -436,6 +436,7 @@ describe('api: watch', () => {
   it('flush: pre watcher watching props should fire before child update', async () => {
     const a = ref(0)
     const b = ref(0)
+    const c = ref(0)
     const calls: string[] = []
 
     const Comp = {
@@ -444,11 +445,22 @@ describe('api: watch', () => {
         watch(
           () => props.a + props.b,
           () => {
-            calls.push('watcher')
+            calls.push('watcher 1')
+            c.value++
+          },
+          { flush: 'pre' }
+        )
+
+        // #1777 chained pre-watcher
+        watch(
+          c,
+          () => {
+            calls.push('watcher 2')
           },
           { flush: 'pre' }
         )
         return () => {
+          c.value
           calls.push('render')
         }
       }
@@ -469,7 +481,7 @@ describe('api: watch', () => {
     a.value++
     b.value++
     await nextTick()
-    expect(calls).toEqual(['render', 'watcher', 'render'])
+    expect(calls).toEqual(['render', 'watcher 1', 'watcher 2', 'render'])
   })
 
   it('deep', async () => {
index 5e7849feba5b0e87bee41519d35f172ecec3e0c3..edec7a747661ee2c37cc46b1f67f097175fc5482 100644 (file)
@@ -1430,7 +1430,7 @@ function baseCreateRenderer(
     instance.next = null
     updateProps(instance, nextVNode.props, prevProps, optimized)
     updateSlots(instance, nextVNode.children)
-    runPreflushJobs()
+    runPreflushJobs(instance.update)
   }
 
   const patchChildren: PatchChildrenFn = (
index c45113d89c53fff0e03f7a56cff9a9bdcda88d83..01afd808e62ba0d0a937c729b734c182d4f2dacb 100644 (file)
@@ -27,6 +27,7 @@ let flushIndex = 0
 let pendingPostFlushCbs: Function[] | null = null
 let pendingPostFlushIndex = 0
 let hasPendingPreFlushJobs = false
+let currentPreFlushParentJob: SchedulerJob | null = null
 
 const RECURSION_LIMIT = 100
 type CountMap = Map<SchedulerJob | Function, number>
@@ -44,9 +45,16 @@ export function queueJob(job: SchedulerJob) {
   // allow it recursively trigger itself - it is the user's responsibility to
   // ensure it doesn't end up in an infinite loop.
   if (
-    !queue.length ||
-    !queue.includes(job, isFlushing && job.cb ? flushIndex + 1 : flushIndex)
+    (!queue.length ||
+      !queue.includes(
+        job,
+        isFlushing && job.cb ? flushIndex + 1 : flushIndex
+      )) &&
+    job !== currentPreFlushParentJob
   ) {
+    if (job.id && job.id > 0) {
+      debugger
+    }
     queue.push(job)
     if ((job.id as number) < 0) hasPendingPreFlushJobs = true
     queueFlush()
@@ -60,16 +68,27 @@ export function invalidateJob(job: SchedulerJob) {
   }
 }
 
-export function runPreflushJobs() {
+/**
+ * Run flush: 'pre' watcher callbacks. This is only called in
+ * `updateComponentPreRender` to cover the case where pre-flush watchers are
+ * triggered by the change of a component's props. This means the scheduler is
+ * already flushing and we are already inside the component's update effect,
+ * right when the render function is about to be called. So if the watcher
+ * triggers the same component to update, we don't want it to be queued (this
+ * is checked via `currentPreFlushParentJob`).
+ */
+export function runPreflushJobs(parentJob: SchedulerJob) {
   if (hasPendingPreFlushJobs) {
+    currentPreFlushParentJob = parentJob
     hasPendingPreFlushJobs = false
-    for (let job, i = queue.length - 1; i > flushIndex; i--) {
+    for (let job, i = flushIndex + 1; i < queue.length; i++) {
       job = queue[i]
       if (job && (job.id as number) < 0) {
         job()
         queue[i] = null
       }
     }
+    currentPreFlushParentJob = null
   }
 }