wip: properly handle invalidated mount/unmount

This commit is contained in:
Evan You 2018-11-10 00:33:41 -05:00
parent 5639022f80
commit f13fbe8686
2 changed files with 103 additions and 60 deletions

View File

@ -228,18 +228,7 @@ export function createRenderer(options: RendererOptions) {
if (__COMPAT__) { if (__COMPAT__) {
mountComponentInstance(vnode, container, isSVG, endNode) mountComponentInstance(vnode, container, isSVG, endNode)
} else { } else {
queueJob(() => { queueJob(() => mountComponentInstance(vnode, container, isSVG, endNode))
const instance = mountComponentInstance(
vnode,
container,
isSVG,
endNode
)
// cleanup if mount is invalidated before committed
return () => {
teardownComponentInstance(instance)
}
})
} }
} }
} }
@ -714,7 +703,7 @@ export function createRenderer(options: RendererOptions) {
isSVG: boolean isSVG: boolean
) { ) {
const refNode = platformNextSibling(getVNodeLastEl(prevVNode)) const refNode = platformNextSibling(getVNodeLastEl(prevVNode))
removeVNode(prevVNode, container) queueRemoveVNode(prevVNode, container)
mount(nextVNode, container, contextVNode, isSVG, refNode) mount(nextVNode, container, contextVNode, isSVG, refNode)
} }
@ -741,10 +730,10 @@ export function createRenderer(options: RendererOptions) {
) )
break break
case ChildrenFlags.NO_CHILDREN: case ChildrenFlags.NO_CHILDREN:
removeVNode(prevChildren as MountedVNode, container) queueRemoveVNode(prevChildren as MountedVNode, container)
break break
default: default:
removeVNode(prevChildren as MountedVNode, container) queueRemoveVNode(prevChildren as MountedVNode, container)
mountArrayChildren( mountArrayChildren(
nextChildren as VNode[], nextChildren as VNode[],
container, container,
@ -782,10 +771,18 @@ export function createRenderer(options: RendererOptions) {
default: default:
// MULTIPLE_CHILDREN // MULTIPLE_CHILDREN
if (nextChildFlags === ChildrenFlags.SINGLE_VNODE) { 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) mount(nextChildren as VNode, container, contextVNode, isSVG, endNode)
} else if (nextChildFlags === ChildrenFlags.NO_CHILDREN) { } else if (nextChildFlags === ChildrenFlags.NO_CHILDREN) {
removeChildren(prevChildren as MountedVNode[], container, endNode) queueRemoveChildren(
prevChildren as MountedVNode[],
container,
endNode
)
} else { } else {
const prevLength = (prevChildren as VNode[]).length const prevLength = (prevChildren as VNode[]).length
const nextLength = (nextChildren as VNode[]).length const nextLength = (nextChildren as VNode[]).length
@ -800,7 +797,11 @@ export function createRenderer(options: RendererOptions) {
) )
} }
} else if (nextLength === 0) { } else if (nextLength === 0) {
removeChildren(prevChildren as MountedVNode[], container, endNode) queueRemoveChildren(
prevChildren as MountedVNode[],
container,
endNode
)
} else if ( } else if (
prevChildFlags === ChildrenFlags.KEYED_VNODES && prevChildFlags === ChildrenFlags.KEYED_VNODES &&
nextChildFlags === ChildrenFlags.KEYED_VNODES nextChildFlags === ChildrenFlags.KEYED_VNODES
@ -858,7 +859,7 @@ export function createRenderer(options: RendererOptions) {
} }
} else if (prevLength > nextLength) { } else if (prevLength > nextLength) {
for (i = commonLength; i < prevLength; i++) { 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) { } else if (j > nextEnd) {
while (j <= prevEnd) { while (j <= prevEnd) {
removeVNode(prevChildren[j++], container) queueRemoveVNode(prevChildren[j++], container)
} }
} else { } else {
let prevStart = j let prevStart = j
@ -952,7 +953,7 @@ export function createRenderer(options: RendererOptions) {
if (canRemoveWholeContent) { if (canRemoveWholeContent) {
canRemoveWholeContent = false canRemoveWholeContent = false
while (i > prevStart) { while (i > prevStart) {
removeVNode(prevChildren[prevStart++], container) queueRemoveVNode(prevChildren[prevStart++], container)
} }
} }
if (pos > j) { if (pos > j) {
@ -966,10 +967,10 @@ export function createRenderer(options: RendererOptions) {
} }
} }
if (!canRemoveWholeContent && j > nextEnd) { if (!canRemoveWholeContent && j > nextEnd) {
removeVNode(prevVNode, container) queueRemoveVNode(prevVNode, container)
} }
} else if (!canRemoveWholeContent) { } else if (!canRemoveWholeContent) {
removeVNode(prevVNode, container) queueRemoveVNode(prevVNode, container)
} }
} }
} else { } else {
@ -991,7 +992,7 @@ export function createRenderer(options: RendererOptions) {
if (canRemoveWholeContent) { if (canRemoveWholeContent) {
canRemoveWholeContent = false canRemoveWholeContent = false
while (i > prevStart) { while (i > prevStart) {
removeVNode(prevChildren[prevStart++], container) queueRemoveVNode(prevChildren[prevStart++], container)
} }
} }
nextVNode = nextChildren[j] nextVNode = nextChildren[j]
@ -1004,16 +1005,16 @@ export function createRenderer(options: RendererOptions) {
patch(prevVNode, nextVNode, container, contextVNode, isSVG) patch(prevVNode, nextVNode, container, contextVNode, isSVG)
patched++ patched++
} else if (!canRemoveWholeContent) { } else if (!canRemoveWholeContent) {
removeVNode(prevVNode, container) queueRemoveVNode(prevVNode, container)
} }
} else if (!canRemoveWholeContent) { } else if (!canRemoveWholeContent) {
removeVNode(prevVNode, container) queueRemoveVNode(prevVNode, container)
} }
} }
} }
// fast-path: if nothing patched remove all old and add all new // fast-path: if nothing patched remove all old and add all new
if (canRemoveWholeContent) { if (canRemoveWholeContent) {
removeChildren(prevChildren as MountedVNode[], container, endNode) queueRemoveChildren(prevChildren as MountedVNode[], container, endNode)
mountArrayChildren( mountArrayChildren(
nextChildren, nextChildren,
container, container,
@ -1125,13 +1126,13 @@ export function createRenderer(options: RendererOptions) {
} }
} else if (flags & VNodeFlags.PORTAL) { } else if (flags & VNodeFlags.PORTAL) {
if (childFlags & ChildrenFlags.MULTIPLE_VNODES) { if (childFlags & ChildrenFlags.MULTIPLE_VNODES) {
removeChildren( queueRemoveChildren(
children as MountedVNode[], children as MountedVNode[],
vnode.tag as RenderNode, vnode.tag as RenderNode,
null null
) )
} else if (childFlags === ChildrenFlags.SINGLE_VNODE) { } else if (childFlags === ChildrenFlags.SINGLE_VNODE) {
removeVNode(children as MountedVNode, vnode.tag as RenderNode) queueRemoveVNode(children as MountedVNode, vnode.tag as RenderNode)
} }
} }
if (ref) { 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) { function removeVNode(vnode: MountedVNode, container: RenderNode) {
unmount(vnode) unmount(vnode)
const { el, flags, children, childFlags } = vnode const { el, flags, children, childFlags } = vnode
@ -1163,7 +1168,7 @@ export function createRenderer(options: RendererOptions) {
removeVNode(children as MountedVNode, container) removeVNode(children as MountedVNode, container)
break break
case ChildrenFlags.NO_CHILDREN: case ChildrenFlags.NO_CHILDREN:
queueNodeOp([platformRemoveChild, container, el]) platformRemoveChild(container, el)
break break
default: default:
for (let i = 0; i < (children as MountedVNode[]).length; i++) { for (let i = 0; i < (children as MountedVNode[]).length; i++) {
@ -1171,12 +1176,20 @@ export function createRenderer(options: RendererOptions) {
} }
} }
} else { } else {
queueNodeOp([platformRemoveChild, container, el]) platformRemoveChild(container, el)
} }
} }
;(vnode as any).el = null ;(vnode as any).el = null
} }
function queueRemoveChildren(
children: MountedVNode[],
container: RenderNode,
refNode: RenderNode | null
) {
queueNodeOp([removeChildren, children, container, refNode])
}
function removeChildren( function removeChildren(
children: MountedVNode[], children: MountedVNode[],
container: RenderNode, container: RenderNode,
@ -1184,7 +1197,7 @@ export function createRenderer(options: RendererOptions) {
) { ) {
unmountArrayChildren(children) unmountArrayChildren(children)
if (refNode === null) { if (refNode === null) {
queueNodeOp([platformClearContent, container]) platformClearContent(container)
} else { } else {
for (let i = 0; i < children.length; i++) { for (let i = 0; i < children.length; i++) {
removeVNode(children[i], container) removeVNode(children[i], container)
@ -1199,7 +1212,7 @@ export function createRenderer(options: RendererOptions) {
container: RenderNode | null, container: RenderNode | null,
isSVG: boolean, isSVG: boolean,
endNode: RenderNode | null endNode: RenderNode | null
): ComponentInstance { ): Function {
if (__DEV__) { if (__DEV__) {
pushWarningContext(vnode) pushWarningContext(vnode)
} }
@ -1265,7 +1278,6 @@ export function createRenderer(options: RendererOptions) {
) )
} }
// this will be executed synchronously right here
instance.$vnode = renderInstanceRoot(instance) as MountedVNode instance.$vnode = renderInstanceRoot(instance) as MountedVNode
queuePostCommitCb(() => { queuePostCommitCb(() => {
@ -1283,11 +1295,10 @@ export function createRenderer(options: RendererOptions) {
if (mounted) { if (mounted) {
callLifecycleHookWithHandler(mounted, $proxy, ErrorTypes.MOUNTED) callLifecycleHookWithHandler(mounted, $proxy, ErrorTypes.MOUNTED)
} }
instance._mounted = true
}) })
mount(instance.$vnode, container, vnode as MountedVNode, isSVG, endNode) mount(instance.$vnode, container, vnode as MountedVNode, isSVG, endNode)
instance._mounted = true
} }
}, autorunOptions) }, autorunOptions)
@ -1295,7 +1306,10 @@ export function createRenderer(options: RendererOptions) {
popWarningContext() popWarningContext()
} }
return instance // cleanup if mount is invalidated before committed
return () => {
teardownComponentInstance(instance)
}
} }
function updateComponentInstance( function updateComponentInstance(
@ -1321,11 +1335,10 @@ export function createRenderer(options: RendererOptions) {
) )
} }
const nextVNode = (instance.$vnode = renderInstanceRoot( const nextVNode = renderInstanceRoot(instance) as MountedVNode
instance
) as MountedVNode)
queuePostCommitCb(() => { queuePostCommitCb(() => {
instance.$vnode = nextVNode
const el = nextVNode.el as RenderNode const el = nextVNode.el as RenderNode
if (__COMPAT__) { if (__COMPAT__) {
// expose __vue__ for devtools // expose __vue__ for devtools
@ -1492,7 +1505,7 @@ export function createRenderer(options: RendererOptions) {
patch(prevVNode, vnode, container, null, false) patch(prevVNode, vnode, container, null, false)
container.vnode = vnode container.vnode = vnode
} else { } else {
removeVNode(prevVNode, container) queueRemoveVNode(prevVNode, container)
container.vnode = null container.vnode = null
} }
} }

View File

@ -7,15 +7,16 @@ const enum Priorities {
} }
const enum JobStatus { const enum JobStatus {
PENDING_PATCH = 1, IDLE = 0,
PENDING_COMMIT, PENDING_PATCH,
COMMITED PENDING_COMMIT
} }
interface Job extends Function { interface Job extends Function {
status: JobStatus status: JobStatus
ops: Op[] ops: Op[]
post: Function[] post: Function[]
children: Job[]
cleanup: Function | null cleanup: Function | null
expiration: number expiration: number
} }
@ -100,11 +101,15 @@ let hasPendingFlush = false
export function queueJob(rawJob: Function) { export function queueJob(rawJob: Function) {
const job = rawJob as Job const job = rawJob as Job
if (currentJob) {
currentJob.children.push(job)
}
// 1. let's see if this invalidates any work that // 1. let's see if this invalidates any work that
// has already been done. // has already been done.
if (job.status === JobStatus.PENDING_COMMIT) { if (job.status === JobStatus.PENDING_COMMIT) {
// pending commit job invalidated // pending commit job invalidated
invalidateJob(job) invalidateJob(job)
requeueInvalidatedJob(job)
} else if (job.status !== JobStatus.PENDING_PATCH) { } else if (job.status !== JobStatus.PENDING_PATCH) {
// a new job // a new job
insertNewJob(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) { export function queuePostCommitCb(fn: Function) {
if (currentJob) { if (currentJob) {
currentJob.post.push(fn) 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) { function insertNewJob(job: Job) {
job.ops = job.ops || [] job.ops = job.ops || []
job.post = job.post || [] 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) patchQueue.push(job)
job.status = JobStatus.PENDING_PATCH job.status = JobStatus.PENDING_PATCH
} }
function invalidateJob(job: Job) { function invalidateJob(job: Job) {
job.ops.length = 0 // recursively invalidate all child jobs
job.post.length = 0 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) { if (job.cleanup) {
job.cleanup() job.cleanup()
job.cleanup = null job.cleanup = null
} }
resetJob(job)
// remove from commit queue // remove from commit queue
// and move it back to the patch queue
commitQueue.splice(commitQueue.indexOf(job), 1) commitQueue.splice(commitQueue.indexOf(job), 1)
// With varying priorities we should insert job at correct position job.status = JobStatus.IDLE
// 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
} }
function patchJob(job: Job) { function patchJob(job: Job) {
@ -235,13 +266,12 @@ function commitJob(job: Job) {
for (let i = 0; i < ops.length; i++) { for (let i = 0; i < ops.length; i++) {
applyOp(ops[i]) applyOp(ops[i])
} }
ops.length = 0
// queue post commit cbs // queue post commit cbs
if (post) { if (post) {
postCommitQueue.push(...post) postCommitQueue.push(...post)
post.length = 0
} }
job.status = JobStatus.COMMITED resetJob(job)
job.status = JobStatus.IDLE
} }
function applyOp(op: Op) { function applyOp(op: Op) {