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__) {
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
}
}

View 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) {