]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: properly handle invalidated mount/unmount
authorEvan You <yyx990803@gmail.com>
Sat, 10 Nov 2018 05:33:41 +0000 (00:33 -0500)
committerEvan You <yyx990803@gmail.com>
Sat, 10 Nov 2018 05:33:41 +0000 (00:33 -0500)
packages/runtime-core/src/createRenderer.ts
packages/scheduler/src/experimental.ts

index fd6452346e9d3ca453458463e49cd4f282e04613..215b1a62c20b5ddc6d9c8add3b21b641f70d0b32 100644 (file)
@@ -228,18 +228,7 @@ export function createRenderer(options: RendererOptions) {
       if (__COMPAT__) {
         mountComponentInstance(vnode, container, isSVG, endNode)
       } else {
-        queueJob(() => {
-          const instance = mountComponentInstance(
-            vnode,
-            container,
-            isSVG,
-            endNode
-          )
-          // cleanup if mount is invalidated before committed
-          return () => {
-            teardownComponentInstance(instance)
-          }
-        })
+        queueJob(() => mountComponentInstance(vnode, container, isSVG, endNode))
       }
     }
   }
@@ -714,7 +703,7 @@ export function createRenderer(options: RendererOptions) {
     isSVG: boolean
   ) {
     const refNode = platformNextSibling(getVNodeLastEl(prevVNode))
-    removeVNode(prevVNode, container)
+    queueRemoveVNode(prevVNode, container)
     mount(nextVNode, container, contextVNode, isSVG, refNode)
   }
 
@@ -741,10 +730,10 @@ export function createRenderer(options: RendererOptions) {
             )
             break
           case ChildrenFlags.NO_CHILDREN:
-            removeVNode(prevChildren as MountedVNode, container)
+            queueRemoveVNode(prevChildren as MountedVNode, container)
             break
           default:
-            removeVNode(prevChildren as MountedVNode, container)
+            queueRemoveVNode(prevChildren as MountedVNode, container)
             mountArrayChildren(
               nextChildren as VNode[],
               container,
@@ -782,10 +771,18 @@ export function createRenderer(options: RendererOptions) {
       default:
         // MULTIPLE_CHILDREN
         if (nextChildFlags === ChildrenFlags.SINGLE_VNODE) {
-          removeChildren(prevChildren as MountedVNode[], container, endNode)
+          queueRemoveChildren(
+            prevChildren as MountedVNode[],
+            container,
+            endNode
+          )
           mount(nextChildren as VNode, container, contextVNode, isSVG, endNode)
         } else if (nextChildFlags === ChildrenFlags.NO_CHILDREN) {
-          removeChildren(prevChildren as MountedVNode[], container, endNode)
+          queueRemoveChildren(
+            prevChildren as MountedVNode[],
+            container,
+            endNode
+          )
         } else {
           const prevLength = (prevChildren as VNode[]).length
           const nextLength = (nextChildren as VNode[]).length
@@ -800,7 +797,11 @@ export function createRenderer(options: RendererOptions) {
               )
             }
           } else if (nextLength === 0) {
-            removeChildren(prevChildren as MountedVNode[], container, endNode)
+            queueRemoveChildren(
+              prevChildren as MountedVNode[],
+              container,
+              endNode
+            )
           } else if (
             prevChildFlags === ChildrenFlags.KEYED_VNODES &&
             nextChildFlags === ChildrenFlags.KEYED_VNODES
@@ -858,7 +859,7 @@ export function createRenderer(options: RendererOptions) {
       }
     } else if (prevLength > nextLength) {
       for (i = commonLength; i < prevLength; i++) {
-        removeVNode(prevChildren[i], container)
+        queueRemoveVNode(prevChildren[i], container)
       }
     }
   }
@@ -923,7 +924,7 @@ export function createRenderer(options: RendererOptions) {
       }
     } else if (j > nextEnd) {
       while (j <= prevEnd) {
-        removeVNode(prevChildren[j++], container)
+        queueRemoveVNode(prevChildren[j++], container)
       }
     } else {
       let prevStart = j
@@ -952,7 +953,7 @@ export function createRenderer(options: RendererOptions) {
                 if (canRemoveWholeContent) {
                   canRemoveWholeContent = false
                   while (i > prevStart) {
-                    removeVNode(prevChildren[prevStart++], container)
+                    queueRemoveVNode(prevChildren[prevStart++], container)
                   }
                 }
                 if (pos > j) {
@@ -966,10 +967,10 @@ export function createRenderer(options: RendererOptions) {
               }
             }
             if (!canRemoveWholeContent && j > nextEnd) {
-              removeVNode(prevVNode, container)
+              queueRemoveVNode(prevVNode, container)
             }
           } else if (!canRemoveWholeContent) {
-            removeVNode(prevVNode, container)
+            queueRemoveVNode(prevVNode, container)
           }
         }
       } else {
@@ -991,7 +992,7 @@ export function createRenderer(options: RendererOptions) {
               if (canRemoveWholeContent) {
                 canRemoveWholeContent = false
                 while (i > prevStart) {
-                  removeVNode(prevChildren[prevStart++], container)
+                  queueRemoveVNode(prevChildren[prevStart++], container)
                 }
               }
               nextVNode = nextChildren[j]
@@ -1004,16 +1005,16 @@ export function createRenderer(options: RendererOptions) {
               patch(prevVNode, nextVNode, container, contextVNode, isSVG)
               patched++
             } else if (!canRemoveWholeContent) {
-              removeVNode(prevVNode, container)
+              queueRemoveVNode(prevVNode, container)
             }
           } else if (!canRemoveWholeContent) {
-            removeVNode(prevVNode, container)
+            queueRemoveVNode(prevVNode, container)
           }
         }
       }
       // fast-path: if nothing patched remove all old and add all new
       if (canRemoveWholeContent) {
-        removeChildren(prevChildren as MountedVNode[], container, endNode)
+        queueRemoveChildren(prevChildren as MountedVNode[], container, endNode)
         mountArrayChildren(
           nextChildren,
           container,
@@ -1125,13 +1126,13 @@ export function createRenderer(options: RendererOptions) {
       }
     } else if (flags & VNodeFlags.PORTAL) {
       if (childFlags & ChildrenFlags.MULTIPLE_VNODES) {
-        removeChildren(
+        queueRemoveChildren(
           children as MountedVNode[],
           vnode.tag as RenderNode,
           null
         )
       } else if (childFlags === ChildrenFlags.SINGLE_VNODE) {
-        removeVNode(children as MountedVNode, vnode.tag as RenderNode)
+        queueRemoveVNode(children as MountedVNode, vnode.tag as RenderNode)
       }
     }
     if (ref) {
@@ -1153,6 +1154,10 @@ export function createRenderer(options: RendererOptions) {
     }
   }
 
+  function queueRemoveVNode(vnode: MountedVNode, container: RenderNode) {
+    queueNodeOp([removeVNode, vnode, container])
+  }
+
   function removeVNode(vnode: MountedVNode, container: RenderNode) {
     unmount(vnode)
     const { el, flags, children, childFlags } = vnode
@@ -1163,7 +1168,7 @@ export function createRenderer(options: RendererOptions) {
             removeVNode(children as MountedVNode, container)
             break
           case ChildrenFlags.NO_CHILDREN:
-            queueNodeOp([platformRemoveChild, container, el])
+            platformRemoveChild(container, el)
             break
           default:
             for (let i = 0; i < (children as MountedVNode[]).length; i++) {
@@ -1171,12 +1176,20 @@ export function createRenderer(options: RendererOptions) {
             }
         }
       } else {
-        queueNodeOp([platformRemoveChild, container, el])
+        platformRemoveChild(container, el)
       }
     }
     ;(vnode as any).el = null
   }
 
+  function queueRemoveChildren(
+    children: MountedVNode[],
+    container: RenderNode,
+    refNode: RenderNode | null
+  ) {
+    queueNodeOp([removeChildren, children, container, refNode])
+  }
+
   function removeChildren(
     children: MountedVNode[],
     container: RenderNode,
@@ -1184,7 +1197,7 @@ export function createRenderer(options: RendererOptions) {
   ) {
     unmountArrayChildren(children)
     if (refNode === null) {
-      queueNodeOp([platformClearContent, container])
+      platformClearContent(container)
     } else {
       for (let i = 0; i < children.length; i++) {
         removeVNode(children[i], container)
@@ -1199,7 +1212,7 @@ export function createRenderer(options: RendererOptions) {
     container: RenderNode | null,
     isSVG: boolean,
     endNode: RenderNode | null
-  ): ComponentInstance {
+  ): Function {
     if (__DEV__) {
       pushWarningContext(vnode)
     }
@@ -1265,7 +1278,6 @@ export function createRenderer(options: RendererOptions) {
           )
         }
 
-        // this will be executed synchronously right here
         instance.$vnode = renderInstanceRoot(instance) as MountedVNode
 
         queuePostCommitCb(() => {
@@ -1283,11 +1295,10 @@ export function createRenderer(options: RendererOptions) {
           if (mounted) {
             callLifecycleHookWithHandler(mounted, $proxy, ErrorTypes.MOUNTED)
           }
+          instance._mounted = true
         })
 
         mount(instance.$vnode, container, vnode as MountedVNode, isSVG, endNode)
-
-        instance._mounted = true
       }
     }, autorunOptions)
 
@@ -1295,7 +1306,10 @@ export function createRenderer(options: RendererOptions) {
       popWarningContext()
     }
 
-    return instance
+    // cleanup if mount is invalidated before committed
+    return () => {
+      teardownComponentInstance(instance)
+    }
   }
 
   function updateComponentInstance(
@@ -1321,11 +1335,10 @@ export function createRenderer(options: RendererOptions) {
       )
     }
 
-    const nextVNode = (instance.$vnode = renderInstanceRoot(
-      instance
-    ) as MountedVNode)
+    const nextVNode = renderInstanceRoot(instance) as MountedVNode
 
     queuePostCommitCb(() => {
+      instance.$vnode = nextVNode
       const el = nextVNode.el as RenderNode
       if (__COMPAT__) {
         // expose __vue__ for devtools
@@ -1492,7 +1505,7 @@ export function createRenderer(options: RendererOptions) {
         patch(prevVNode, vnode, container, null, false)
         container.vnode = vnode
       } else {
-        removeVNode(prevVNode, container)
+        queueRemoveVNode(prevVNode, container)
         container.vnode = null
       }
     }
index e7a010b12e63c5cc3cc9857d3d89cf00e2292955..aaadb567ea3fde7582afb37991c9542eafe3e4fe 100644 (file)
@@ -7,15 +7,16 @@ const enum Priorities {
 }
 
 const enum JobStatus {
-  PENDING_PATCH = 1,
-  PENDING_COMMIT,
-  COMMITED
+  IDLE = 0,
+  PENDING_PATCH,
+  PENDING_COMMIT
 }
 
 interface Job extends Function {
   status: JobStatus
   ops: Op[]
   post: Function[]
+  children: Job[]
   cleanup: Function | null
   expiration: number
 }
@@ -100,11 +101,15 @@ let hasPendingFlush = false
 
 export function queueJob(rawJob: Function) {
   const job = rawJob as Job
+  if (currentJob) {
+    currentJob.children.push(job)
+  }
   // 1. let's see if this invalidates any work that
   // has already been done.
   if (job.status === JobStatus.PENDING_COMMIT) {
     // pending commit job invalidated
     invalidateJob(job)
+    requeueInvalidatedJob(job)
   } else if (job.status !== JobStatus.PENDING_PATCH) {
     // a new job
     insertNewJob(job)
@@ -115,6 +120,20 @@ export function queueJob(rawJob: Function) {
   }
 }
 
+function requeueInvalidatedJob(job: Job) {
+  // With varying priorities we should insert job at correct position
+  // based on expiration time.
+  for (let i = 0; i < patchQueue.length; i++) {
+    if (job.expiration < patchQueue[i].expiration) {
+      patchQueue.splice(i, 0, job)
+      job.status = JobStatus.PENDING_PATCH
+      return
+    }
+  }
+  patchQueue.push(job)
+  job.status = JobStatus.PENDING_PATCH
+}
+
 export function queuePostCommitCb(fn: Function) {
   if (currentJob) {
     currentJob.post.push(fn)
@@ -190,33 +209,45 @@ function flush(): void {
   }
 }
 
+function resetJob(job: Job) {
+  job.ops.length = 0
+  job.post.length = 0
+  job.children.length = 0
+}
+
 function insertNewJob(job: Job) {
   job.ops = job.ops || []
   job.post = job.post || []
-  job.expiration = getNow() + Priorities.NORMAL
+  job.children = job.children || []
+  resetJob(job)
+  // inherit parent job's expiration deadline
+  job.expiration = currentJob
+    ? currentJob.expiration
+    : getNow() + Priorities.NORMAL
   patchQueue.push(job)
   job.status = JobStatus.PENDING_PATCH
 }
 
 function invalidateJob(job: Job) {
-  job.ops.length = 0
-  job.post.length = 0
+  // recursively invalidate all child jobs
+  const { children } = job
+  for (let i = 0; i < children.length; i++) {
+    const child = children[i]
+    if (child.status === JobStatus.PENDING_COMMIT) {
+      invalidateJob(child)
+    } else if (child.status === JobStatus.PENDING_PATCH) {
+      patchQueue.splice(patchQueue.indexOf(child), 1)
+      child.status = JobStatus.IDLE
+    }
+  }
   if (job.cleanup) {
     job.cleanup()
     job.cleanup = null
   }
+  resetJob(job)
   // remove from commit queue
-  // and move it back to the patch queue
   commitQueue.splice(commitQueue.indexOf(job), 1)
-  // With varying priorities we should insert job at correct position
-  // based on expiration time.
-  for (let i = 0; i < patchQueue.length; i++) {
-    if (job.expiration < patchQueue[i].expiration) {
-      patchQueue.splice(i, 0, job)
-      break
-    }
-  }
-  job.status = JobStatus.PENDING_PATCH
+  job.status = JobStatus.IDLE
 }
 
 function patchJob(job: Job) {
@@ -235,13 +266,12 @@ function commitJob(job: Job) {
   for (let i = 0; i < ops.length; i++) {
     applyOp(ops[i])
   }
-  ops.length = 0
   // queue post commit cbs
   if (post) {
     postCommitQueue.push(...post)
-    post.length = 0
   }
-  job.status = JobStatus.COMMITED
+  resetJob(job)
+  job.status = JobStatus.IDLE
 }
 
 function applyOp(op: Op) {