diff --git a/packages/runtime-core/src/createRenderer.ts b/packages/runtime-core/src/createRenderer.ts index fd645234..215b1a62 100644 --- a/packages/runtime-core/src/createRenderer.ts +++ b/packages/runtime-core/src/createRenderer.ts @@ -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 } } diff --git a/packages/scheduler/src/experimental.ts b/packages/scheduler/src/experimental.ts index e7a010b1..aaadb567 100644 --- a/packages/scheduler/src/experimental.ts +++ b/packages/scheduler/src/experimental.ts @@ -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) {