wip: properly handle invalidated mount/unmount
This commit is contained in:
parent
5639022f80
commit
f13fbe8686
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user