]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-core/scheduler): allow component render functions to trigger itself
authorEvan You <yyx990803@gmail.com>
Thu, 13 Aug 2020 21:27:14 +0000 (17:27 -0400)
committerEvan You <yyx990803@gmail.com>
Thu, 13 Aug 2020 21:42:47 +0000 (17:42 -0400)
fix #1801

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

index 59855a6917943557a48d8f7d20136b4a71d8554b..1811ce6b390d449c1e7dcb15c3cc5cc21e0245d2 100644 (file)
@@ -451,7 +451,7 @@ describe('scheduler', () => {
     expect(count).toBe(1)
   })
 
-  test('should allow watcher callbacks to trigger itself', async () => {
+  test('should allow explicitly marked jobs to trigger itself', async () => {
     // normal job
     let count = 0
     const job = () => {
@@ -460,7 +460,7 @@ describe('scheduler', () => {
         queueJob(job)
       }
     }
-    job.cb = true
+    job.allowRecurse = true
     queueJob(job)
     await nextTick()
     expect(count).toBe(3)
@@ -472,7 +472,7 @@ describe('scheduler', () => {
         queuePostFlushCb(cb)
       }
     }
-    cb.cb = true
+    cb.allowRecurse = true
     queuePostFlushCb(cb)
     await nextTick()
     expect(count).toBe(5)
index cdc3df48b50403b8db2fcd216842517619731dba..ac797b89ea380fa96c30540ae330c5abdee284aa 100644 (file)
@@ -261,7 +261,7 @@ function doWatch(
 
   // important: mark the job as a watcher callback so that scheduler knows it
   // it is allowed to self-trigger (#1727)
-  job.cb = !!cb
+  job.allowRecurse = !!cb
 
   let scheduler: (job: () => any) => void
   if (flush === 'sync') {
index 8627cb00fea307ab8c82289fb849c634ec4ae470..cb1e3869345b441ebd6e98c1f81996b4f43956fb 100644 (file)
@@ -41,7 +41,8 @@ import {
   queuePostFlushCb,
   flushPostFlushCbs,
   invalidateJob,
-  flushPreFlushCbs
+  flushPreFlushCbs,
+  SchedulerJob
 } from './scheduler'
 import { effect, stop, ReactiveEffectOptions, isRef } from '@vue/reactivity'
 import { updateProps } from './componentProps'
@@ -1429,6 +1430,8 @@ function baseCreateRenderer(
         }
       }
     }, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
+    // #1801 mark it to allow recursive updates
+    ;(instance.update as SchedulerJob).allowRecurse = true
   }
 
   const updateComponentPreRender = (
index c9c0e47e4837761255d58b698a8c92973d461086..1f168b7363ee33834b87832bfcc8c8535e2f983f 100644 (file)
@@ -8,12 +8,18 @@ export interface SchedulerJob {
    */
   id?: number
   /**
-   * Indicates this is a watch() callback and is allowed to trigger itself.
-   * A watch callback doesn't track its dependencies so if it triggers itself
-   * again, it's likely intentional and it is the user's responsibility to
-   * perform recursive state mutation that eventually stabilizes.
+   * Indicates whether the job is allowed to recursively trigger itself.
+   * By default, a job cannot trigger itself because some built-in method calls,
+   * e.g. Array.prototype.push actually performs reads as well (#1740) which
+   * can lead to confusing infinite loops.
+   * The allowed cases are component render functions and watch callbacks.
+   * Render functions may update child component props, which in turn trigger
+   * flush: "pre" watch callbacks that mutates state that the parent relies on
+   * (#1801). Watch callbacks doesn't track its dependencies so if it triggers
+   * itself again, it's likely intentional and it is the user's responsibility
+   * to perform recursive state mutation that eventually stabilizes (#1727).
    */
-  cb?: boolean
+  allowRecurse?: boolean
 }
 
 let isFlushing = false
@@ -54,7 +60,7 @@ export function queueJob(job: SchedulerJob) {
     (!queue.length ||
       !queue.includes(
         job,
-        isFlushing && job.cb ? flushIndex + 1 : flushIndex
+        isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex
       )) &&
     job !== currentPreFlushParentJob
   ) {
@@ -86,7 +92,10 @@ function queueCb(
   if (!isArray(cb)) {
     if (
       !activeQueue ||
-      !activeQueue.includes(cb, (cb as SchedulerJob).cb ? index + 1 : index)
+      !activeQueue.includes(
+        cb,
+        (cb as SchedulerJob).allowRecurse ? index + 1 : index
+      )
     ) {
       pendingQueue.push(cb)
     }