]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(watch): pre-flush watcher watching props should trigger before component update
authorEvan You <yyx990803@gmail.com>
Mon, 3 Aug 2020 20:49:13 +0000 (16:49 -0400)
committerEvan You <yyx990803@gmail.com>
Mon, 3 Aug 2020 20:49:30 +0000 (16:49 -0400)
fix #1763

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

index 97f686ad995a567ad6ff5e2befe272fa75cd7b81..e6172e7f5680d3a6fff7a7e2bbd5d6e81ced45d5 100644 (file)
@@ -432,6 +432,46 @@ describe('api: watch', () => {
     expect(cb).toHaveBeenCalledTimes(1)
   })
 
+  // #1763
+  it('flush: pre watcher watching props should fire before child update', async () => {
+    const a = ref(0)
+    const b = ref(0)
+    const calls: string[] = []
+
+    const Comp = {
+      props: ['a', 'b'],
+      setup(props: any) {
+        watch(
+          () => props.a + props.b,
+          () => {
+            calls.push('watcher')
+          },
+          { flush: 'pre' }
+        )
+        return () => {
+          calls.push('render')
+        }
+      }
+    }
+
+    const App = {
+      render() {
+        return h(Comp, { a: a.value, b: b.value })
+      }
+    }
+
+    render(h(App), nodeOps.createElement('div'))
+    expect(calls).toEqual(['render'])
+
+    // both props are updated
+    // should trigger pre-flush watcher first and only once
+    // then trigger child render
+    a.value++
+    b.value++
+    await nextTick()
+    expect(calls).toEqual(['render', 'watcher', 'render'])
+  })
+
   it('deep', async () => {
     const state = reactive({
       nested: {
index ed832253182e1de89d876932860b6941418f9196..5e7849feba5b0e87bee41519d35f172ecec3e0c3 100644 (file)
@@ -40,7 +40,8 @@ import {
   queueJob,
   queuePostFlushCb,
   flushPostFlushCbs,
-  invalidateJob
+  invalidateJob,
+  runPreflushJobs
 } from './scheduler'
 import { effect, stop, ReactiveEffectOptions, isRef } from '@vue/reactivity'
 import { updateProps } from './componentProps'
@@ -1429,6 +1430,7 @@ function baseCreateRenderer(
     instance.next = null
     updateProps(instance, nextVNode.props, prevProps, optimized)
     updateSlots(instance, nextVNode.children)
+    runPreflushJobs()
   }
 
   const patchChildren: PatchChildrenFn = (
index a6933bcccd59e06f40ea9ecffd41376e424cbdd4..cbfedf0a07ce68d8fdb2ed54f7e04a381232e04c 100644 (file)
@@ -26,6 +26,7 @@ let isFlushPending = false
 let flushIndex = 0
 let pendingPostFlushCbs: Function[] | null = null
 let pendingPostFlushIndex = 0
+let hasPendingPreFlushJobs = false
 
 const RECURSION_LIMIT = 100
 type CountMap = Map<SchedulerJob | Function, number>
@@ -47,6 +48,7 @@ export function queueJob(job: SchedulerJob) {
     !queue.includes(job, job.cb ? flushIndex + 1 : flushIndex)
   ) {
     queue.push(job)
+    if ((job.id as number) < 0) hasPendingPreFlushJobs = true
     queueFlush()
   }
 }
@@ -58,6 +60,19 @@ export function invalidateJob(job: SchedulerJob) {
   }
 }
 
+export function runPreflushJobs() {
+  if (hasPendingPreFlushJobs) {
+    hasPendingPreFlushJobs = false
+    for (let job, i = queue.length - 1; i > flushIndex; i--) {
+      job = queue[i]
+      if (job && (job.id as number) < 0) {
+        job()
+        queue[i] = null
+      }
+    }
+  }
+}
+
 export function queuePostFlushCb(cb: Function | Function[]) {
   if (!isArray(cb)) {
     if (