vue3-yuanma/packages/runtime-core/src/createRenderer.ts

1615 lines
45 KiB
TypeScript
Raw Normal View History

import {
2018-11-14 00:03:35 +08:00
effect as createReactiveEffect,
stop as stopReactiveEffect,
ReactiveEffect,
immutable,
2018-11-14 00:03:35 +08:00
ReactiveEffectOptions
} from '@vue/observer'
import {
queueJob,
handleSchedulerError,
nextTick,
2018-11-14 00:03:35 +08:00
queuePostEffect,
2018-11-13 01:42:35 +08:00
flushEffects,
queueNodeOp
} from '@vue/scheduler'
2018-09-19 23:35:38 +08:00
import { VNodeFlags, ChildrenFlags } from './flags'
import { EMPTY_OBJ, reservedPropRE, isString } from '@vue/shared'
2018-09-19 23:35:38 +08:00
import {
VNode,
MountedVNode,
RenderNode,
createTextVNode,
cloneVNode,
VNodeChildren
2018-09-19 23:35:38 +08:00
} from './vdom'
import { ComponentInstance } from './component'
2019-02-27 07:04:52 +08:00
import {
createComponentInstance,
teardownComponentInstance
} from './componentInstance'
2018-09-19 23:35:38 +08:00
import {
renderInstanceRoot,
renderFunctionalRoot,
shouldUpdateComponent,
getReasonForComponentUpdate
2019-02-27 07:04:52 +08:00
} from './componentRenderUtils'
2018-09-27 05:10:34 +08:00
import { KeepAliveSymbol } from './optional/keepAlive'
import { pushWarningContext, popWarningContext, warn } from './warning'
import { resolveProps } from './componentProps'
import {
handleError,
ErrorTypes,
callLifecycleHookWithHandler
} from './errorHandling'
2018-09-19 23:35:38 +08:00
2018-11-01 05:58:06 +08:00
export interface NodeOps {
2018-09-20 12:17:20 +08:00
createElement: (tag: string, isSVG?: boolean) => any
createText: (text: string) => any
setText: (node: any, text: string) => void
appendChild: (parent: any, child: any) => void
insertBefore: (parent: any, child: any, ref: any) => void
removeChild: (parent: any, child: any) => void
clearContent: (node: any) => void
parentNode: (node: any) => any
nextSibling: (node: any) => any
querySelector: (selector: string) => any
}
2018-11-01 05:58:06 +08:00
export interface PatchDataFunction {
2018-09-20 12:17:20 +08:00
(
2018-09-19 23:35:38 +08:00
el: any,
key: string,
prevValue: any,
nextValue: any,
preVNode: VNode | null,
nextVNode: VNode,
isSVG: boolean,
// passed for DOM operations that removes child content
// e.g. innerHTML & textContent
unmountChildren: (children: VNode[], childFlags: ChildrenFlags) => void
2018-09-20 12:17:20 +08:00
): void
}
2018-11-01 05:58:06 +08:00
export interface RendererOptions {
2018-09-20 12:17:20 +08:00
nodeOps: NodeOps
patchData: PatchDataFunction
2018-09-19 23:35:38 +08:00
}
export interface FunctionalHandle {
2018-11-13 13:29:18 +08:00
prev: VNode
next: VNode
2018-11-14 00:03:35 +08:00
update: ReactiveEffect
2018-11-13 14:07:13 +08:00
container: RenderNode | null
}
handleSchedulerError(err => handleError(err, null, ErrorTypes.SCHEDULER))
2018-09-19 23:35:38 +08:00
// The whole mounting / patching / unmouting logic is placed inside this
// single function so that we can create multiple renderes with different
// platform definitions. This allows for use cases like creating a test
// renderer alongside an actual renderer.
export function createRenderer(options: RendererOptions) {
const {
nodeOps: {
createElement: platformCreateElement,
createText: platformCreateText,
setText: platformSetText,
appendChild: platformAppendChild,
insertBefore: platformInsertBefore,
removeChild: platformRemoveChild,
clearContent: platformClearContent,
parentNode: platformParentNode,
nextSibling: platformNextSibling,
querySelector: platformQuerySelector
},
patchData: platformPatchData
2018-09-19 23:35:38 +08:00
} = options
2018-11-13 11:07:55 +08:00
function queueInsertOrAppend(
2018-09-19 23:35:38 +08:00
container: RenderNode,
newNode: RenderNode,
refNode: RenderNode | null
2018-09-19 23:35:38 +08:00
) {
if (refNode === null) {
queueNodeOp([platformAppendChild, container, newNode])
2018-09-19 23:35:38 +08:00
} else {
2018-11-10 07:08:53 +08:00
queueNodeOp([platformInsertBefore, container, newNode, refNode])
2018-09-19 23:35:38 +08:00
}
}
// mounting ------------------------------------------------------------------
function mount(
vnode: VNode,
container: RenderNode | null,
2018-10-11 01:13:27 +08:00
contextVNode: MountedVNode | null,
2018-09-19 23:35:38 +08:00
isSVG: boolean,
endNode: RenderNode | null,
ownerArray?: VNode[],
index?: number
) {
2018-09-19 23:35:38 +08:00
const { flags } = vnode
if (flags & VNodeFlags.ELEMENT) {
2018-10-11 01:13:27 +08:00
mountElement(vnode, container, contextVNode, isSVG, endNode)
} else if (flags & VNodeFlags.COMPONENT) {
mountComponent(vnode, container, contextVNode, isSVG, endNode)
2018-09-19 23:35:38 +08:00
} else if (flags & VNodeFlags.TEXT) {
mountText(vnode, container, endNode)
2018-09-19 23:35:38 +08:00
} else if (flags & VNodeFlags.FRAGMENT) {
2018-10-11 01:13:27 +08:00
mountFragment(vnode, container, contextVNode, isSVG, endNode)
2018-09-19 23:35:38 +08:00
} else if (flags & VNodeFlags.PORTAL) {
2018-10-11 01:13:27 +08:00
mountPortal(vnode, container, contextVNode)
2018-09-19 23:35:38 +08:00
}
}
function mountArrayChildren(
children: VNode[],
container: RenderNode | null,
2018-10-11 01:13:27 +08:00
contextVNode: MountedVNode | null,
2018-09-19 23:35:38 +08:00
isSVG: boolean,
endNode: RenderNode | null
2018-09-19 23:35:38 +08:00
) {
for (let i = 0; i < children.length; i++) {
const child = getNextVNode(children, i)
mount(child, container, contextVNode, isSVG, endNode)
2018-09-19 23:35:38 +08:00
}
}
function mountElement(
vnode: VNode,
container: RenderNode | null,
2018-10-11 01:13:27 +08:00
contextVNode: MountedVNode | null,
2018-09-19 23:35:38 +08:00
isSVG: boolean,
endNode: RenderNode | null
) {
2018-09-19 23:35:38 +08:00
const { flags, tag, data, children, childFlags, ref } = vnode
isSVG = isSVG || (flags & VNodeFlags.ELEMENT_SVG) > 0
2018-11-13 11:07:55 +08:00
// element creation is not deferred since it doesn't produce
// user-affecting side effects until inserted into the DOM
2018-09-19 23:35:38 +08:00
const el = (vnode.el = platformCreateElement(tag as string, isSVG))
if (data != null) {
for (const key in data) {
2018-11-13 11:07:55 +08:00
if (!reservedPropRE.test(key)) {
platformPatchData(
el,
key,
null,
data[key],
null,
vnode,
isSVG,
unmountChildren
)
}
2018-09-19 23:35:38 +08:00
}
2018-09-26 01:39:19 +08:00
if (data.vnodeBeforeMount) {
data.vnodeBeforeMount(vnode)
}
2018-09-19 23:35:38 +08:00
}
if (childFlags !== ChildrenFlags.NO_CHILDREN) {
const hasSVGChildren = isSVG && tag !== 'foreignObject'
if (childFlags & ChildrenFlags.SINGLE_VNODE) {
2018-10-29 02:22:52 +08:00
mount(children as VNode, el, contextVNode, hasSVGChildren, null)
2018-09-19 23:35:38 +08:00
} else if (childFlags & ChildrenFlags.MULTIPLE_VNODES) {
mountArrayChildren(
children as VNode[],
el,
2018-10-11 01:13:27 +08:00
contextVNode,
2018-09-19 23:35:38 +08:00
hasSVGChildren,
2018-10-29 02:22:52 +08:00
null
2018-09-19 23:35:38 +08:00
)
}
}
if (container != null) {
2018-11-13 11:07:55 +08:00
queueInsertOrAppend(container, el, endNode)
2018-09-19 23:35:38 +08:00
}
if (ref) {
2018-11-14 00:03:35 +08:00
queuePostEffect(() => {
2018-11-02 13:09:00 +08:00
ref(el)
})
2018-09-19 23:35:38 +08:00
}
2018-09-26 01:39:19 +08:00
if (data != null && data.vnodeMounted) {
2018-11-14 00:03:35 +08:00
queuePostEffect(() => {
2018-09-26 01:39:19 +08:00
data.vnodeMounted(vnode)
})
}
2018-09-19 23:35:38 +08:00
}
function mountComponent(
2018-09-19 23:35:38 +08:00
vnode: VNode,
container: RenderNode | null,
2018-10-11 01:13:27 +08:00
contextVNode: MountedVNode | null,
2018-09-19 23:35:38 +08:00
isSVG: boolean,
endNode: RenderNode | null
) {
2018-10-11 01:13:27 +08:00
vnode.contextVNode = contextVNode
const { flags } = vnode
if (flags & VNodeFlags.COMPONENT_STATEFUL) {
2018-10-16 00:41:18 +08:00
mountStatefulComponent(vnode, container, isSVG, endNode)
} else {
2018-10-16 00:41:18 +08:00
mountFunctionalComponent(vnode, container, isSVG, endNode)
}
}
function mountStatefulComponent(
vnode: VNode,
container: RenderNode | null,
isSVG: boolean,
endNode: RenderNode | null
) {
2018-10-03 03:53:22 +08:00
if (vnode.flags & VNodeFlags.COMPONENT_STATEFUL_KEPT_ALIVE) {
// kept-alive
activateComponentInstance(vnode, container, endNode)
2018-09-19 23:35:38 +08:00
} else {
2018-11-02 13:21:38 +08:00
if (__COMPAT__) {
mountComponentInstance(vnode, container, isSVG, endNode)
2018-11-02 13:21:38 +08:00
} else {
queueJob(() => mountComponentInstance(vnode, container, isSVG, endNode))
2018-11-02 13:21:38 +08:00
}
2018-09-19 23:35:38 +08:00
}
}
2018-10-03 03:53:22 +08:00
function mountFunctionalComponent(
vnode: VNode,
container: RenderNode | null,
isSVG: boolean,
endNode: RenderNode | null
) {
if (__DEV__ && vnode.ref) {
warn(
`cannot use ref on a functional component because there is no ` +
`instance to reference to.`
)
}
const handle: FunctionalHandle = (vnode.handle = {
2018-11-13 13:29:18 +08:00
prev: vnode,
next: null as any,
2018-11-13 14:07:13 +08:00
update: null as any,
container
})
2018-11-02 13:21:38 +08:00
const doMount = () => {
2018-11-14 00:03:35 +08:00
handle.update = createReactiveEffect(
() => {
2018-11-13 13:29:18 +08:00
if (!handle.next) {
// initial mount
if (__DEV__) {
pushWarningContext(vnode)
}
2018-11-13 13:29:18 +08:00
const subTree = (vnode.children = renderFunctionalRoot(vnode))
2018-11-14 00:03:35 +08:00
queuePostEffect(() => {
2018-11-02 13:21:38 +08:00
vnode.el = subTree.el as RenderNode
})
mount(subTree, container, vnode as MountedVNode, isSVG, endNode)
2018-11-13 14:07:13 +08:00
handle.next = vnode
if (__DEV__) {
popWarningContext()
}
} else {
updateFunctionalComponent(handle, isSVG)
}
},
{
2018-11-13 13:29:18 +08:00
scheduler: queueJob
}
)
2018-11-02 13:21:38 +08:00
}
// we are using vnode.ref to store the functional component's update job
if (__COMPAT__) {
doMount()
} else {
queueJob(() => {
doMount()
// cleanup if mount is invalidated before committed
return () => {
2018-11-14 00:03:35 +08:00
stopReactiveEffect(handle.update)
}
})
2018-11-02 13:21:38 +08:00
}
2018-10-03 03:53:22 +08:00
}
function updateFunctionalComponent(handle: FunctionalHandle, isSVG: boolean) {
2018-11-13 13:29:18 +08:00
const { prev, next } = handle
if (__DEV__) {
2018-11-13 13:29:18 +08:00
pushWarningContext(next)
}
2018-11-13 14:07:13 +08:00
const prevTree = prev.children as MountedVNode
2018-11-13 13:29:18 +08:00
const nextTree = (next.children = renderFunctionalRoot(next))
2018-11-14 00:03:35 +08:00
queuePostEffect(() => {
2018-11-13 13:29:18 +08:00
next.el = nextTree.el
})
patch(
2018-11-13 14:07:13 +08:00
prevTree,
nextTree,
2018-11-13 14:07:13 +08:00
handle.container as RenderNode,
2018-11-13 13:29:18 +08:00
next as MountedVNode,
isSVG
)
if (__DEV__) {
popWarningContext()
}
}
2018-09-19 23:35:38 +08:00
function mountText(
vnode: VNode,
container: RenderNode | null,
endNode: RenderNode | null
) {
2018-09-19 23:35:38 +08:00
const el = (vnode.el = platformCreateText(vnode.children as string))
if (container != null) {
2018-11-13 11:07:55 +08:00
queueInsertOrAppend(container, el, endNode)
2018-09-19 23:35:38 +08:00
}
}
function mountFragment(
vnode: VNode,
container: RenderNode | null,
2018-10-11 01:13:27 +08:00
contextVNode: MountedVNode | null,
2018-09-19 23:35:38 +08:00
isSVG: boolean,
endNode: RenderNode | null
) {
2018-09-19 23:35:38 +08:00
const { children, childFlags } = vnode
switch (childFlags) {
case ChildrenFlags.SINGLE_VNODE:
2018-11-14 00:03:35 +08:00
queuePostEffect(() => {
2018-11-02 13:09:00 +08:00
vnode.el = (children as MountedVNode).el
})
2018-10-11 01:13:27 +08:00
mount(children as VNode, container, contextVNode, isSVG, endNode)
break
case ChildrenFlags.NO_CHILDREN:
const placeholder = createTextVNode('')
mountText(placeholder, container, null)
vnode.el = placeholder.el
break
default:
2018-11-14 00:03:35 +08:00
queuePostEffect(() => {
2018-11-02 13:09:00 +08:00
vnode.el = (children as MountedVNode[])[0].el
})
mountArrayChildren(
children as VNode[],
container,
2018-10-11 01:13:27 +08:00
contextVNode,
isSVG,
endNode
)
2018-09-19 23:35:38 +08:00
}
}
function mountPortal(
vnode: VNode,
container: RenderNode | null,
2018-10-11 01:13:27 +08:00
contextVNode: MountedVNode | null
) {
2018-09-19 23:35:38 +08:00
const { tag, children, childFlags, ref } = vnode
const target = isString(tag) ? platformQuerySelector(tag) : tag
2018-09-19 23:35:38 +08:00
if (__DEV__ && !target) {
// TODO warn poartal target not found
}
if (childFlags & ChildrenFlags.SINGLE_VNODE) {
2018-10-11 01:13:27 +08:00
mount(children as VNode, target as RenderNode, contextVNode, false, null)
2018-09-19 23:35:38 +08:00
} else if (childFlags & ChildrenFlags.MULTIPLE_VNODES) {
mountArrayChildren(
children as VNode[],
target as RenderNode,
2018-10-11 01:13:27 +08:00
contextVNode,
2018-09-19 23:35:38 +08:00
false,
null
)
}
if (ref) {
2018-11-14 00:03:35 +08:00
queuePostEffect(() => {
2018-11-02 13:09:00 +08:00
ref(target)
})
2018-09-19 23:35:38 +08:00
}
const placeholder = createTextVNode('')
mountText(placeholder, container, null)
vnode.el = placeholder.el
2018-09-19 23:35:38 +08:00
}
// patching ------------------------------------------------------------------
2018-11-13 11:07:55 +08:00
function queuePatchData(
2018-11-01 05:58:06 +08:00
el: RenderNode | (() => RenderNode),
2018-09-19 23:35:38 +08:00
key: string,
prevValue: any,
nextValue: any,
preVNode: VNode | null,
nextVNode: VNode,
isSVG: boolean
) {
2018-11-13 11:07:55 +08:00
if (!reservedPropRE.test(key)) {
queueNodeOp([
platformPatchData,
el,
key,
prevValue,
nextValue,
preVNode,
nextVNode,
isSVG,
unmountChildren
])
2018-09-19 23:35:38 +08:00
}
}
function patch(
prevVNode: MountedVNode,
2018-09-19 23:35:38 +08:00
nextVNode: VNode,
container: RenderNode,
2018-10-11 01:13:27 +08:00
contextVNode: MountedVNode | null,
2018-09-19 23:35:38 +08:00
isSVG: boolean
) {
const nextFlags = nextVNode.flags
const prevFlags = prevVNode.flags
if (prevFlags !== nextFlags) {
2018-10-11 01:13:27 +08:00
replaceVNode(prevVNode, nextVNode, container, contextVNode, isSVG)
2018-09-19 23:35:38 +08:00
} else if (nextFlags & VNodeFlags.ELEMENT) {
2018-10-11 01:13:27 +08:00
patchElement(prevVNode, nextVNode, container, contextVNode, isSVG)
2018-09-19 23:35:38 +08:00
} else if (nextFlags & VNodeFlags.COMPONENT) {
2018-10-11 01:13:27 +08:00
patchComponent(prevVNode, nextVNode, container, contextVNode, isSVG)
2018-09-19 23:35:38 +08:00
} else if (nextFlags & VNodeFlags.TEXT) {
patchText(prevVNode, nextVNode)
} else if (nextFlags & VNodeFlags.FRAGMENT) {
2018-10-11 01:13:27 +08:00
patchFragment(prevVNode, nextVNode, container, contextVNode, isSVG)
2018-09-19 23:35:38 +08:00
} else if (nextFlags & VNodeFlags.PORTAL) {
2018-10-11 01:13:27 +08:00
patchPortal(prevVNode, nextVNode, contextVNode)
2018-09-19 23:35:38 +08:00
}
}
function patchElement(
prevVNode: MountedVNode,
2018-09-19 23:35:38 +08:00
nextVNode: VNode,
container: RenderNode,
2018-10-11 01:13:27 +08:00
contextVNode: MountedVNode | null,
2018-09-19 23:35:38 +08:00
isSVG: boolean
) {
2018-11-13 11:42:34 +08:00
const { flags, tag, clonedFrom } = nextVNode
// cloned vnodes pointing to the same original.
// these are hoisted static trees so just skip entirely
if (
clonedFrom !== null &&
(clonedFrom === prevVNode || clonedFrom === prevVNode.clonedFrom)
) {
nextVNode.el = prevVNode.el
return
}
2018-09-19 23:35:38 +08:00
isSVG = isSVG || (flags & VNodeFlags.ELEMENT_SVG) > 0
if (prevVNode.tag !== tag) {
2018-10-11 01:13:27 +08:00
replaceVNode(prevVNode, nextVNode, container, contextVNode, isSVG)
2018-09-19 23:35:38 +08:00
return
}
const el = (nextVNode.el = prevVNode.el)
2018-09-19 23:35:38 +08:00
const prevData = prevVNode.data
const nextData = nextVNode.data
2018-09-26 01:39:19 +08:00
if (nextData != null && nextData.vnodeBeforeUpdate) {
nextData.vnodeBeforeUpdate(nextVNode, prevVNode)
}
// patch data
2018-09-19 23:35:38 +08:00
if (prevData !== nextData) {
const prevDataOrEmpty = prevData || EMPTY_OBJ
const nextDataOrEmpty = nextData || EMPTY_OBJ
if (nextDataOrEmpty !== EMPTY_OBJ) {
for (const key in nextDataOrEmpty) {
const prevValue = prevDataOrEmpty[key]
const nextValue = nextDataOrEmpty[key]
if (prevValue !== nextValue) {
2018-11-13 11:07:55 +08:00
queuePatchData(
2018-09-19 23:35:38 +08:00
el,
key,
prevValue,
nextValue,
prevVNode,
nextVNode,
isSVG
)
}
}
}
if (prevDataOrEmpty !== EMPTY_OBJ) {
for (const key in prevDataOrEmpty) {
const prevValue = prevDataOrEmpty[key]
if (prevValue != null && !nextDataOrEmpty.hasOwnProperty(key)) {
2018-11-13 11:07:55 +08:00
queuePatchData(
el,
key,
prevValue,
null,
prevVNode,
nextVNode,
isSVG
)
2018-09-19 23:35:38 +08:00
}
}
}
}
// children
patchChildren(
prevVNode.childFlags,
nextVNode.childFlags,
prevVNode.children,
nextVNode.children,
el,
2018-10-11 01:13:27 +08:00
contextVNode,
2018-09-19 23:35:38 +08:00
isSVG && nextVNode.tag !== 'foreignObject',
null
)
2018-09-26 01:39:19 +08:00
if (nextData != null && nextData.vnodeUpdated) {
// TODO fix me
// vnodeUpdatedHooks.push(() => {
// nextData.vnodeUpdated(nextVNode, prevVNode)
// })
2018-09-26 01:39:19 +08:00
}
2018-09-19 23:35:38 +08:00
}
function patchComponent(
prevVNode: MountedVNode,
2018-09-19 23:35:38 +08:00
nextVNode: VNode,
container: RenderNode,
2018-10-11 01:13:27 +08:00
contextVNode: MountedVNode | null,
2018-09-19 23:35:38 +08:00
isSVG: boolean
) {
2018-10-11 01:13:27 +08:00
nextVNode.contextVNode = contextVNode
2018-09-19 23:35:38 +08:00
const { tag, flags } = nextVNode
if (tag !== prevVNode.tag) {
2018-10-11 01:13:27 +08:00
replaceVNode(prevVNode, nextVNode, container, contextVNode, isSVG)
2018-09-19 23:35:38 +08:00
} else if (flags & VNodeFlags.COMPONENT_STATEFUL) {
patchStatefulComponent(prevVNode, nextVNode)
} else {
2018-11-13 14:07:13 +08:00
patchFunctionalComponent(prevVNode, nextVNode, container)
2018-09-19 23:35:38 +08:00
}
}
function patchStatefulComponent(prevVNode: MountedVNode, nextVNode: VNode) {
const { data: prevData } = prevVNode
const { data: nextData, slots: nextSlots } = nextVNode
2018-09-19 23:35:38 +08:00
const instance = (nextVNode.children =
2018-10-09 23:37:24 +08:00
prevVNode.children) as ComponentInstance
2018-09-19 23:35:38 +08:00
if (nextData !== prevData) {
const { 0: props, 1: attrs } = resolveProps(
nextData,
instance.$options.props
)
instance.$props = __DEV__ ? immutable(props) : props
instance.$attrs = __DEV__ ? immutable(attrs) : attrs
}
instance.$slots = nextSlots || EMPTY_OBJ
instance.$parentVNode = nextVNode as MountedVNode
2018-09-19 23:35:38 +08:00
if (shouldUpdateComponent(prevVNode, nextVNode)) {
if (__DEV__ && instance.$options.renderTriggered) {
callLifecycleHookWithHandler(
instance.$options.renderTriggered,
instance.$proxy,
ErrorTypes.RENDER_TRIGGERED,
getReasonForComponentUpdate(prevVNode, nextVNode)
)
}
2018-09-19 23:35:38 +08:00
instance.$forceUpdate()
} else if (instance.$vnode.flags & VNodeFlags.COMPONENT) {
2018-10-11 01:13:27 +08:00
instance.$vnode.contextVNode = nextVNode
2018-09-19 23:35:38 +08:00
}
nextVNode.el = instance.$vnode.el
}
2018-11-13 14:07:13 +08:00
function patchFunctionalComponent(
prevVNode: MountedVNode,
nextVNode: VNode,
container: RenderNode
) {
const prevTree = prevVNode.children as VNode
const handle = (nextVNode.handle = prevVNode.handle as FunctionalHandle)
2018-11-13 13:29:18 +08:00
handle.prev = prevVNode
handle.next = nextVNode
2018-11-13 14:07:13 +08:00
handle.container = container
2018-09-19 23:35:38 +08:00
if (shouldUpdateComponent(prevVNode, nextVNode)) {
2018-11-13 13:29:18 +08:00
queueJob(handle.update)
2018-09-19 23:35:38 +08:00
} else if (prevTree.flags & VNodeFlags.COMPONENT) {
// functional component returned another component
2018-10-11 01:13:27 +08:00
prevTree.contextVNode = nextVNode
2018-09-19 23:35:38 +08:00
}
}
function patchFragment(
prevVNode: MountedVNode,
2018-09-19 23:35:38 +08:00
nextVNode: VNode,
container: RenderNode,
2018-10-11 01:13:27 +08:00
contextVNode: MountedVNode | null,
2018-09-19 23:35:38 +08:00
isSVG: boolean
) {
// determine the tail node of the previous fragment,
// then retrieve its next sibling to use as the end node for patchChildren.
const endNode = platformNextSibling(getVNodeLastEl(prevVNode))
const { childFlags, children } = nextVNode
2018-11-14 00:03:35 +08:00
queuePostEffect(() => {
2018-11-02 13:09:00 +08:00
switch (childFlags) {
case ChildrenFlags.SINGLE_VNODE:
nextVNode.el = (children as MountedVNode).el
break
case ChildrenFlags.NO_CHILDREN:
nextVNode.el = prevVNode.el
break
default:
nextVNode.el = (children as MountedVNode[])[0].el
}
})
2018-09-19 23:35:38 +08:00
patchChildren(
prevVNode.childFlags,
childFlags,
prevVNode.children,
children,
container,
2018-10-11 01:13:27 +08:00
contextVNode,
2018-09-19 23:35:38 +08:00
isSVG,
endNode
2018-09-19 23:35:38 +08:00
)
}
function getVNodeLastEl(vnode: MountedVNode): RenderNode {
const { el, flags, children, childFlags } = vnode
if (flags & VNodeFlags.FRAGMENT) {
if (childFlags & ChildrenFlags.SINGLE_VNODE) {
return getVNodeLastEl(children as MountedVNode)
} else if (childFlags & ChildrenFlags.MULTIPLE_VNODES) {
return getVNodeLastEl(
(children as MountedVNode[])[(children as MountedVNode[]).length - 1]
)
} else {
return el
2018-09-19 23:35:38 +08:00
}
} else {
return el
2018-09-19 23:35:38 +08:00
}
}
function patchText(prevVNode: MountedVNode, nextVNode: VNode) {
2018-09-19 23:35:38 +08:00
const el = (nextVNode.el = prevVNode.el) as RenderNode
const nextText = nextVNode.children
if (nextText !== prevVNode.children) {
queueNodeOp([platformSetText, el, nextText])
2018-09-19 23:35:38 +08:00
}
}
function patchPortal(
prevVNode: MountedVNode,
2018-09-19 23:35:38 +08:00
nextVNode: VNode,
2018-10-11 01:13:27 +08:00
contextVNode: MountedVNode | null
2018-09-19 23:35:38 +08:00
) {
const prevContainer = prevVNode.tag as RenderNode
const nextContainer = nextVNode.tag as RenderNode
const nextChildren = nextVNode.children
patchChildren(
prevVNode.childFlags,
nextVNode.childFlags,
prevVNode.children,
nextChildren,
prevContainer,
2018-10-11 01:13:27 +08:00
contextVNode,
2018-09-19 23:35:38 +08:00
false,
null
)
nextVNode.el = prevVNode.el
if (nextContainer !== prevContainer) {
switch (nextVNode.childFlags) {
case ChildrenFlags.SINGLE_VNODE:
insertVNode(nextChildren as MountedVNode, nextContainer, null)
2018-09-19 23:35:38 +08:00
break
case ChildrenFlags.NO_CHILDREN:
break
default:
for (let i = 0; i < (nextChildren as MountedVNode[]).length; i++) {
insertVNode(
(nextChildren as MountedVNode[])[i],
nextContainer,
null
)
2018-09-19 23:35:38 +08:00
}
break
}
}
}
function replaceVNode(
prevVNode: MountedVNode,
2018-09-19 23:35:38 +08:00
nextVNode: VNode,
container: RenderNode,
2018-10-11 01:13:27 +08:00
contextVNode: MountedVNode | null,
2018-09-19 23:35:38 +08:00
isSVG: boolean
) {
const refNode = platformNextSibling(getVNodeLastEl(prevVNode))
queueRemoveVNode(prevVNode, container)
2018-10-11 01:13:27 +08:00
mount(nextVNode, container, contextVNode, isSVG, refNode)
2018-09-19 23:35:38 +08:00
}
function patchChildren(
prevChildFlags: ChildrenFlags,
nextChildFlags: ChildrenFlags,
prevChildren: VNodeChildren,
nextChildren: VNodeChildren,
container: RenderNode,
2018-10-11 01:13:27 +08:00
contextVNode: MountedVNode | null,
2018-09-19 23:35:38 +08:00
isSVG: boolean,
endNode: RenderNode | null
2018-09-19 23:35:38 +08:00
) {
switch (prevChildFlags) {
case ChildrenFlags.SINGLE_VNODE:
switch (nextChildFlags) {
case ChildrenFlags.SINGLE_VNODE:
patch(
prevChildren as MountedVNode,
2018-09-19 23:35:38 +08:00
nextChildren as VNode,
container,
2018-10-11 01:13:27 +08:00
contextVNode,
2018-09-19 23:35:38 +08:00
isSVG
)
break
case ChildrenFlags.NO_CHILDREN:
queueRemoveVNode(prevChildren as MountedVNode, container)
2018-09-19 23:35:38 +08:00
break
default:
queueRemoveVNode(prevChildren as MountedVNode, container)
2018-09-19 23:35:38 +08:00
mountArrayChildren(
nextChildren as VNode[],
container,
2018-10-11 01:13:27 +08:00
contextVNode,
2018-09-19 23:35:38 +08:00
isSVG,
endNode
)
break
}
break
case ChildrenFlags.NO_CHILDREN:
switch (nextChildFlags) {
case ChildrenFlags.SINGLE_VNODE:
mount(
nextChildren as VNode,
container,
2018-10-11 01:13:27 +08:00
contextVNode,
2018-09-19 23:35:38 +08:00
isSVG,
endNode
)
break
case ChildrenFlags.NO_CHILDREN:
break
default:
mountArrayChildren(
nextChildren as VNode[],
container,
2018-10-11 01:13:27 +08:00
contextVNode,
2018-09-19 23:35:38 +08:00
isSVG,
endNode
)
break
}
break
default:
// MULTIPLE_CHILDREN
if (nextChildFlags === ChildrenFlags.SINGLE_VNODE) {
queueRemoveChildren(
prevChildren as MountedVNode[],
container,
endNode
)
2018-10-11 01:13:27 +08:00
mount(nextChildren as VNode, container, contextVNode, isSVG, endNode)
2018-09-19 23:35:38 +08:00
} else if (nextChildFlags === ChildrenFlags.NO_CHILDREN) {
queueRemoveChildren(
prevChildren as MountedVNode[],
container,
endNode
)
2018-09-19 23:35:38 +08:00
} else {
const prevLength = (prevChildren as VNode[]).length
const nextLength = (nextChildren as VNode[]).length
if (prevLength === 0) {
if (nextLength > 0) {
mountArrayChildren(
nextChildren as VNode[],
container,
2018-10-11 01:13:27 +08:00
contextVNode,
2018-09-19 23:35:38 +08:00
isSVG,
endNode
)
}
} else if (nextLength === 0) {
queueRemoveChildren(
prevChildren as MountedVNode[],
container,
endNode
)
2018-09-19 23:35:38 +08:00
} else if (
prevChildFlags === ChildrenFlags.KEYED_VNODES &&
nextChildFlags === ChildrenFlags.KEYED_VNODES
) {
patchKeyedChildren(
prevChildren as MountedVNode[],
2018-09-19 23:35:38 +08:00
nextChildren as VNode[],
container,
prevLength,
nextLength,
2018-10-11 01:13:27 +08:00
contextVNode,
2018-09-19 23:35:38 +08:00
isSVG,
endNode
)
} else {
patchNonKeyedChildren(
prevChildren as MountedVNode[],
2018-09-19 23:35:38 +08:00
nextChildren as VNode[],
container,
prevLength,
nextLength,
2018-10-11 01:13:27 +08:00
contextVNode,
2018-09-19 23:35:38 +08:00
isSVG,
endNode
)
}
}
break
}
}
function patchNonKeyedChildren(
prevChildren: MountedVNode[],
2018-09-19 23:35:38 +08:00
nextChildren: VNode[],
container: RenderNode,
prevLength: number,
nextLength: number,
2018-10-11 01:13:27 +08:00
contextVNode: MountedVNode | null,
2018-09-19 23:35:38 +08:00
isSVG: boolean,
endNode: RenderNode | null
2018-09-19 23:35:38 +08:00
) {
const commonLength = prevLength > nextLength ? nextLength : prevLength
let i = 0
let nextChild
let prevChild
for (i; i < commonLength; i++) {
nextChild = getNextVNode(nextChildren, i)
2018-09-19 23:35:38 +08:00
prevChild = prevChildren[i]
2018-10-11 01:13:27 +08:00
patch(prevChild, nextChild, container, contextVNode, isSVG)
prevChildren[i] = nextChild as MountedVNode
2018-09-19 23:35:38 +08:00
}
if (prevLength < nextLength) {
for (i = commonLength; i < nextLength; i++) {
nextChild = getNextVNode(nextChildren, i)
mount(nextChild, container, contextVNode, isSVG, endNode)
2018-09-19 23:35:38 +08:00
}
} else if (prevLength > nextLength) {
for (i = commonLength; i < prevLength; i++) {
queueRemoveVNode(prevChildren[i], container)
2018-09-19 23:35:38 +08:00
}
}
}
function patchKeyedChildren(
prevChildren: MountedVNode[],
2018-09-19 23:35:38 +08:00
nextChildren: VNode[],
container: RenderNode,
prevLength: number,
nextLength: number,
2018-10-11 01:13:27 +08:00
contextVNode: MountedVNode | null,
2018-09-19 23:35:38 +08:00
isSVG: boolean,
endNode: RenderNode | null
2018-09-19 23:35:38 +08:00
) {
let prevEnd = prevLength - 1
let nextEnd = nextLength - 1
let i
let j = 0
let prevVNode = prevChildren[j]
let nextVNode = getNextVNode(nextChildren, j)
2018-09-19 23:35:38 +08:00
let nextPos
outer: {
// Sync nodes with the same key at the beginning.
while (prevVNode.key === nextVNode.key) {
2018-10-11 01:13:27 +08:00
patch(prevVNode, nextVNode, container, contextVNode, isSVG)
prevChildren[j] = nextVNode as MountedVNode
2018-09-19 23:35:38 +08:00
j++
if (j > prevEnd || j > nextEnd) {
break outer
}
prevVNode = prevChildren[j]
nextVNode = getNextVNode(nextChildren, j)
2018-09-19 23:35:38 +08:00
}
prevVNode = prevChildren[prevEnd]
nextVNode = getNextVNode(nextChildren, nextEnd)
2018-09-19 23:35:38 +08:00
// Sync nodes with the same key at the end.
while (prevVNode.key === nextVNode.key) {
2018-10-11 01:13:27 +08:00
patch(prevVNode, nextVNode, container, contextVNode, isSVG)
prevChildren[prevEnd] = nextVNode as MountedVNode
2018-09-19 23:35:38 +08:00
prevEnd--
nextEnd--
if (j > prevEnd || j > nextEnd) {
break outer
}
prevVNode = prevChildren[prevEnd]
nextVNode = getNextVNode(nextChildren, nextEnd)
2018-09-19 23:35:38 +08:00
}
}
if (j > prevEnd) {
if (j <= nextEnd) {
nextPos = nextEnd + 1
const nextNode =
nextPos < nextLength ? nextChildren[nextPos].el : endNode
while (j <= nextEnd) {
nextVNode = getNextVNode(nextChildren, j)
j++
2018-10-11 01:13:27 +08:00
mount(nextVNode, container, contextVNode, isSVG, nextNode)
2018-09-19 23:35:38 +08:00
}
}
} else if (j > nextEnd) {
while (j <= prevEnd) {
queueRemoveVNode(prevChildren[j++], container)
2018-09-19 23:35:38 +08:00
}
} else {
let prevStart = j
const nextStart = j
const prevLeft = prevEnd - j + 1
const nextLeft = nextEnd - j + 1
const sources: number[] = []
for (i = 0; i < nextLeft; i++) {
sources.push(0)
}
// Keep track if its possible to remove whole DOM using textContent = ''
let canRemoveWholeContent = prevLeft === prevLength
let moved = false
let pos = 0
let patched = 0
// When sizes are small, just loop them through
if (nextLength < 4 || (prevLeft | nextLeft) < 32) {
for (i = prevStart; i <= prevEnd; i++) {
prevVNode = prevChildren[i]
if (patched < nextLeft) {
for (j = nextStart; j <= nextEnd; j++) {
nextVNode = getNextVNode(nextChildren, j)
2018-09-19 23:35:38 +08:00
if (prevVNode.key === nextVNode.key) {
sources[j - nextStart] = i + 1
if (canRemoveWholeContent) {
canRemoveWholeContent = false
while (i > prevStart) {
queueRemoveVNode(prevChildren[prevStart++], container)
2018-09-19 23:35:38 +08:00
}
}
if (pos > j) {
moved = true
} else {
pos = j
}
2018-10-11 01:13:27 +08:00
patch(prevVNode, nextVNode, container, contextVNode, isSVG)
2018-09-19 23:35:38 +08:00
patched++
break
}
}
if (!canRemoveWholeContent && j > nextEnd) {
queueRemoveVNode(prevVNode, container)
2018-09-19 23:35:38 +08:00
}
} else if (!canRemoveWholeContent) {
queueRemoveVNode(prevVNode, container)
2018-09-19 23:35:38 +08:00
}
}
} else {
const keyIndex: Record<string, number> = {}
// Map keys by their index
for (i = nextStart; i <= nextEnd; i++) {
keyIndex[nextChildren[i].key as string] = i
}
// Try to patch same keys
for (i = prevStart; i <= prevEnd; i++) {
prevVNode = prevChildren[i]
if (patched < nextLeft) {
j = keyIndex[prevVNode.key as string]
if (j !== void 0) {
if (canRemoveWholeContent) {
canRemoveWholeContent = false
while (i > prevStart) {
queueRemoveVNode(prevChildren[prevStart++], container)
2018-09-19 23:35:38 +08:00
}
}
nextVNode = getNextVNode(nextChildren, j)
2018-09-19 23:35:38 +08:00
sources[j - nextStart] = i + 1
if (pos > j) {
moved = true
} else {
pos = j
}
2018-10-11 01:13:27 +08:00
patch(prevVNode, nextVNode, container, contextVNode, isSVG)
2018-09-19 23:35:38 +08:00
patched++
} else if (!canRemoveWholeContent) {
queueRemoveVNode(prevVNode, container)
2018-09-19 23:35:38 +08:00
}
} else if (!canRemoveWholeContent) {
queueRemoveVNode(prevVNode, container)
2018-09-19 23:35:38 +08:00
}
}
}
// fast-path: if nothing patched remove all old and add all new
if (canRemoveWholeContent) {
queueRemoveChildren(prevChildren as MountedVNode[], container, endNode)
2018-09-19 23:35:38 +08:00
mountArrayChildren(
nextChildren,
container,
2018-10-11 01:13:27 +08:00
contextVNode,
2018-09-19 23:35:38 +08:00
isSVG,
endNode
)
} else {
if (moved) {
const seq = lis(sources)
j = seq.length - 1
for (i = nextLeft - 1; i >= 0; i--) {
if (sources[i] === 0) {
pos = i + nextStart
nextVNode = getNextVNode(nextChildren, pos)
2018-09-19 23:35:38 +08:00
nextPos = pos + 1
mount(
nextVNode,
container,
2018-10-11 01:13:27 +08:00
contextVNode,
2018-09-19 23:35:38 +08:00
isSVG,
nextPos < nextLength ? nextChildren[nextPos].el : endNode
)
} else if (j < 0 || i !== seq[j]) {
pos = i + nextStart
nextVNode = nextChildren[pos]
nextPos = pos + 1
insertVNode(
nextVNode as MountedVNode,
2018-09-19 23:35:38 +08:00
container,
nextPos < nextLength ? nextChildren[nextPos].el : endNode
)
} else {
j--
}
}
} else if (patched !== nextLeft) {
// when patched count doesn't match b length we need to insert those
// new ones loop backwards so we can use insertBefore
for (i = nextLeft - 1; i >= 0; i--) {
if (sources[i] === 0) {
pos = i + nextStart
nextVNode = getNextVNode(nextChildren, pos)
2018-09-19 23:35:38 +08:00
nextPos = pos + 1
mount(
nextVNode,
container,
2018-10-11 01:13:27 +08:00
contextVNode,
2018-09-19 23:35:38 +08:00
isSVG,
nextPos < nextLength ? nextChildren[nextPos].el : endNode
)
}
}
}
}
}
}
function insertVNode(
vnode: MountedVNode,
container: RenderNode,
refNode: RenderNode | null
) {
const { flags, childFlags, children } = vnode
if (flags & VNodeFlags.FRAGMENT) {
switch (childFlags) {
case ChildrenFlags.SINGLE_VNODE:
insertVNode(children as MountedVNode, container, refNode)
break
case ChildrenFlags.NO_CHILDREN:
break
default:
for (let i = 0; i < (children as MountedVNode[]).length; i++) {
insertVNode((children as MountedVNode[])[i], container, refNode)
}
}
} else {
2018-11-13 11:07:55 +08:00
queueInsertOrAppend(container, vnode.el as RenderNode, refNode)
}
}
2018-09-19 23:35:38 +08:00
// unmounting ----------------------------------------------------------------
function unmount(vnode: MountedVNode) {
const { flags, data, children, childFlags, ref, handle } = vnode
2018-09-26 01:39:19 +08:00
const isElement = flags & VNodeFlags.ELEMENT
if (isElement || flags & VNodeFlags.FRAGMENT) {
if (isElement && data != null && data.vnodeBeforeUnmount) {
data.vnodeBeforeUnmount(vnode)
}
2018-09-19 23:35:38 +08:00
unmountChildren(children as VNodeChildren, childFlags)
2018-09-26 01:39:19 +08:00
if (isElement && data != null && data.vnodeUnmounted) {
data.vnodeUnmounted(vnode)
}
2018-09-19 23:35:38 +08:00
} else if (flags & VNodeFlags.COMPONENT) {
if (flags & VNodeFlags.COMPONENT_STATEFUL) {
2018-09-27 06:34:21 +08:00
if (flags & VNodeFlags.COMPONENT_STATEFUL_SHOULD_KEEP_ALIVE) {
2018-10-09 23:37:24 +08:00
deactivateComponentInstance(children as ComponentInstance)
2018-09-27 06:34:21 +08:00
} else {
2018-10-09 23:37:24 +08:00
unmountComponentInstance(children as ComponentInstance)
2018-09-27 05:10:34 +08:00
}
2018-09-19 23:35:38 +08:00
} else {
// functional
2018-11-14 00:03:35 +08:00
stopReactiveEffect((handle as FunctionalHandle).update)
unmount(children as MountedVNode)
2018-09-19 23:35:38 +08:00
}
} else if (flags & VNodeFlags.PORTAL) {
if (childFlags & ChildrenFlags.MULTIPLE_VNODES) {
queueRemoveChildren(
children as MountedVNode[],
vnode.tag as RenderNode,
null
)
2018-09-19 23:35:38 +08:00
} else if (childFlags === ChildrenFlags.SINGLE_VNODE) {
queueRemoveVNode(children as MountedVNode, vnode.tag as RenderNode)
2018-09-19 23:35:38 +08:00
}
}
if (ref) {
ref(null)
}
}
function unmountChildren(children: VNodeChildren, childFlags: ChildrenFlags) {
if (childFlags & ChildrenFlags.MULTIPLE_VNODES) {
unmountArrayChildren(children as MountedVNode[])
2018-09-19 23:35:38 +08:00
} else if (childFlags === ChildrenFlags.SINGLE_VNODE) {
unmount(children as MountedVNode)
2018-09-19 23:35:38 +08:00
}
}
function unmountArrayChildren(children: MountedVNode[]) {
2018-09-19 23:35:38 +08:00
for (let i = 0; i < children.length; i++) {
unmount(children[i])
}
}
function queueRemoveVNode(vnode: MountedVNode, container: RenderNode) {
queueNodeOp([removeVNode, vnode, container])
}
function removeVNode(vnode: MountedVNode, container: RenderNode) {
2018-09-19 23:35:38 +08:00
unmount(vnode)
const { el, flags, children, childFlags } = vnode
if (container && el) {
if (flags & VNodeFlags.FRAGMENT) {
switch (childFlags) {
case ChildrenFlags.SINGLE_VNODE:
removeVNode(children as MountedVNode, container)
break
case ChildrenFlags.NO_CHILDREN:
platformRemoveChild(container, el)
break
default:
for (let i = 0; i < (children as MountedVNode[]).length; i++) {
removeVNode((children as MountedVNode[])[i], container)
}
}
} else {
platformRemoveChild(container, el)
}
;(vnode as any).el = null
2018-09-19 23:35:38 +08:00
}
}
function queueRemoveChildren(
children: MountedVNode[],
container: RenderNode,
refNode: RenderNode | null
) {
queueNodeOp([removeChildren, children, container, refNode])
}
function removeChildren(
children: MountedVNode[],
2018-09-19 23:35:38 +08:00
container: RenderNode,
refNode: RenderNode | null
2018-09-19 23:35:38 +08:00
) {
unmountArrayChildren(children)
if (refNode === null) {
platformClearContent(container)
2018-09-19 23:35:38 +08:00
} else {
for (let i = 0; i < children.length; i++) {
removeVNode(children[i], container)
2018-09-19 23:35:38 +08:00
}
}
}
// Component lifecycle -------------------------------------------------------
function mountComponentInstance(
2018-10-11 01:13:27 +08:00
vnode: VNode,
2018-09-19 23:35:38 +08:00
container: RenderNode | null,
isSVG: boolean,
endNode: RenderNode | null
): Function {
if (__DEV__) {
pushWarningContext(vnode)
}
2018-09-27 05:10:34 +08:00
// a vnode may already have an instance if this is a compat call with
// new Vue()
const instance = ((__COMPAT__ && vnode.children) ||
createComponentInstance(vnode as any)) as ComponentInstance
2018-09-19 23:35:38 +08:00
2018-09-27 05:10:34 +08:00
// inject platform-specific unmount to keep-alive container
if ((vnode.tag as any)[KeepAliveSymbol] === true) {
2018-09-27 05:10:34 +08:00
;(instance as any).$unmount = unmountComponentInstance
}
2018-10-17 07:10:08 +08:00
const {
$proxy,
2018-10-28 10:10:25 +08:00
$options: { beforeMount, renderTracked, renderTriggered }
2018-10-17 07:10:08 +08:00
} = instance
2018-11-13 13:29:18 +08:00
instance.$forceUpdate = () => {
2018-11-14 00:03:35 +08:00
queueJob(instance._update)
2018-11-13 13:29:18 +08:00
}
2018-09-19 23:35:38 +08:00
2018-11-14 00:03:35 +08:00
const effectOptions: ReactiveEffectOptions = {
2018-11-13 13:29:18 +08:00
scheduler: queueJob
}
if (__DEV__) {
if (renderTracked) {
2018-11-14 00:03:35 +08:00
effectOptions.onTrack = event => {
callLifecycleHookWithHandler(
renderTracked,
$proxy,
ErrorTypes.RENDER_TRACKED,
event
)
2018-09-19 23:35:38 +08:00
}
}
if (renderTriggered) {
2018-11-14 00:03:35 +08:00
effectOptions.onTrigger = event => {
callLifecycleHookWithHandler(
renderTriggered,
$proxy,
ErrorTypes.RENDER_TRIGGERED,
event
2018-10-11 01:13:27 +08:00
)
2018-09-19 23:35:38 +08:00
}
}
}
2018-11-14 00:03:35 +08:00
instance._update = createReactiveEffect(() => {
if (instance._unmounted) {
return
}
if (instance._mounted) {
updateComponentInstance(instance, isSVG)
} else {
if (beforeMount) {
callLifecycleHookWithHandler(
beforeMount,
$proxy,
ErrorTypes.BEFORE_MOUNT
)
}
instance.$vnode = renderInstanceRoot(instance) as MountedVNode
2018-11-14 00:03:35 +08:00
queuePostEffect(() => {
vnode.el = instance.$vnode.el
if (__COMPAT__) {
// expose __vue__ for devtools
;(vnode.el as any).__vue__ = instance
}
if (vnode.ref) {
vnode.ref($proxy)
}
// retrieve mounted value after initial render so that we get
// to inject effects in hooks
const { mounted } = instance.$options
if (mounted) {
callLifecycleHookWithHandler(mounted, $proxy, ErrorTypes.MOUNTED)
}
instance._mounted = true
})
mount(instance.$vnode, container, vnode as MountedVNode, isSVG, endNode)
}
2018-11-14 00:03:35 +08:00
}, effectOptions)
2018-09-19 23:35:38 +08:00
if (__DEV__) {
popWarningContext()
}
// cleanup if mount is invalidated before committed
return () => {
teardownComponentInstance(instance)
}
2018-09-19 23:35:38 +08:00
}
2018-10-09 23:37:24 +08:00
function updateComponentInstance(
instance: ComponentInstance,
isSVG: boolean
) {
if (__DEV__ && instance.$parentVNode) {
pushWarningContext(instance.$parentVNode as VNode)
}
2018-10-17 07:10:08 +08:00
const {
$vnode: prevVNode,
$parentVNode,
$proxy,
$options: { beforeUpdate }
2018-10-17 07:10:08 +08:00
} = instance
if (beforeUpdate) {
callLifecycleHookWithHandler(
beforeUpdate,
$proxy,
ErrorTypes.BEFORE_UPDATE,
prevVNode
)
}
const nextVNode = renderInstanceRoot(instance) as MountedVNode
2018-10-05 04:35:07 +08:00
2018-11-14 00:03:35 +08:00
queuePostEffect(() => {
instance.$vnode = nextVNode
2018-11-02 13:09:00 +08:00
const el = nextVNode.el as RenderNode
if (__COMPAT__) {
// expose __vue__ for devtools
;(el as any).__vue__ = instance
}
// recursively update contextVNode el for nested HOCs
if ((nextVNode.flags & VNodeFlags.PORTAL) === 0) {
let vnode = $parentVNode
while (vnode !== null) {
if ((vnode.flags & VNodeFlags.COMPONENT) > 0) {
vnode.el = el
}
vnode = vnode.contextVNode
2018-09-19 23:35:38 +08:00
}
}
2018-11-02 13:09:00 +08:00
const { updated } = instance.$options
if (updated) {
callLifecycleHookWithHandler(
updated,
$proxy,
ErrorTypes.UPDATED,
nextVNode
)
2018-11-02 13:09:00 +08:00
}
// TODO fix me
// if (vnodeUpdatedHooks.length > 0) {
// const vnodeUpdatedHooksForCurrentInstance = vnodeUpdatedHooks.slice()
// vnodeUpdatedHooks.length = 0
// for (let i = 0; i < vnodeUpdatedHooksForCurrentInstance.length; i++) {
// vnodeUpdatedHooksForCurrentInstance[i]()
// }
// }
2018-11-02 13:09:00 +08:00
})
2018-09-26 01:39:19 +08:00
2018-11-02 13:09:00 +08:00
const container = platformParentNode(prevVNode.el) as RenderNode
patch(prevVNode, nextVNode, container, $parentVNode as MountedVNode, isSVG)
if (__DEV__ && instance.$parentVNode) {
popWarningContext()
}
2018-09-19 23:35:38 +08:00
}
2018-10-09 23:37:24 +08:00
function unmountComponentInstance(instance: ComponentInstance) {
2018-09-27 05:10:34 +08:00
if (instance._unmounted) {
return
}
2018-10-17 07:10:08 +08:00
const {
$vnode,
$proxy,
$options: { beforeUnmount, unmounted }
} = instance
if (beforeUnmount) {
callLifecycleHookWithHandler(
beforeUnmount,
$proxy,
ErrorTypes.BEFORE_UNMOUNT
)
2018-10-17 07:10:08 +08:00
}
if ($vnode) {
unmount($vnode)
}
2018-09-19 23:35:38 +08:00
teardownComponentInstance(instance)
2018-09-27 05:10:34 +08:00
instance._unmounted = true
2018-10-17 07:10:08 +08:00
if (unmounted) {
callLifecycleHookWithHandler(unmounted, $proxy, ErrorTypes.UNMOUNTED)
2018-09-19 23:35:38 +08:00
}
}
2018-09-27 06:34:21 +08:00
// Keep Alive ----------------------------------------------------------------
function activateComponentInstance(
vnode: VNode,
container: RenderNode | null,
endNode: RenderNode | null
) {
if (__DEV__) {
pushWarningContext(vnode)
}
2018-10-09 23:37:24 +08:00
const instance = vnode.children as ComponentInstance
vnode.el = instance.$el as RenderNode
if (container != null) {
insertVNode(instance.$vnode, container, endNode)
}
if (__DEV__) {
popWarningContext()
}
2018-11-14 00:03:35 +08:00
queuePostEffect(() => {
2018-09-27 06:34:21 +08:00
callActivatedHook(instance, true)
})
}
2018-10-09 23:37:24 +08:00
function callActivatedHook(instance: ComponentInstance, asRoot: boolean) {
2018-09-27 06:34:21 +08:00
// 1. check if we are inside an inactive parent tree.
if (asRoot) {
instance._inactiveRoot = false
if (isInInactiveTree(instance)) return
}
if (asRoot || !instance._inactiveRoot) {
// 2. recursively call activated on child tree, depth-first
2018-10-17 07:10:08 +08:00
const {
$children,
$proxy,
$options: { activated }
} = instance
2018-09-27 06:34:21 +08:00
for (let i = 0; i < $children.length; i++) {
callActivatedHook($children[i], false)
}
2018-10-17 07:10:08 +08:00
if (activated) {
callLifecycleHookWithHandler(activated, $proxy, ErrorTypes.ACTIVATED)
2018-09-27 06:34:21 +08:00
}
}
}
2018-10-09 23:37:24 +08:00
function deactivateComponentInstance(instance: ComponentInstance) {
2018-09-27 06:34:21 +08:00
callDeactivateHook(instance, true)
}
2018-10-09 23:37:24 +08:00
function callDeactivateHook(instance: ComponentInstance, asRoot: boolean) {
2018-09-27 06:34:21 +08:00
if (asRoot) {
instance._inactiveRoot = true
if (isInInactiveTree(instance)) return
}
if (asRoot || !instance._inactiveRoot) {
// 2. recursively call deactivated on child tree, depth-first
2018-10-17 07:10:08 +08:00
const {
$children,
$proxy,
$options: { deactivated }
} = instance
2018-09-27 06:34:21 +08:00
for (let i = 0; i < $children.length; i++) {
callDeactivateHook($children[i], false)
}
2018-10-17 07:10:08 +08:00
if (deactivated) {
callLifecycleHookWithHandler(
deactivated,
$proxy,
ErrorTypes.DEACTIVATED
)
2018-09-27 06:34:21 +08:00
}
}
}
2018-10-09 23:37:24 +08:00
function isInInactiveTree(instance: ComponentInstance): boolean {
2018-09-27 06:34:21 +08:00
while ((instance = instance.$parent as any) !== null) {
if (instance._inactiveRoot) return true
}
return false
}
2018-09-19 23:35:38 +08:00
// TODO hydrating ------------------------------------------------------------
// API -----------------------------------------------------------------------
2018-11-01 05:58:06 +08:00
function render(vnode: VNode | null, container: any) {
2018-09-19 23:35:38 +08:00
const prevVNode = container.vnode
if (vnode && vnode.el) {
vnode = cloneVNode(vnode)
}
2018-09-19 23:35:38 +08:00
if (prevVNode == null) {
if (vnode) {
mount(vnode, container, null, false, null)
container.vnode = vnode
}
} else {
if (vnode) {
patch(prevVNode, vnode, container, null, false)
container.vnode = vnode
} else {
queueRemoveVNode(prevVNode, container)
2018-09-19 23:35:38 +08:00
container.vnode = null
}
}
2018-11-02 13:21:38 +08:00
if (__COMPAT__) {
2018-11-13 01:42:35 +08:00
flushEffects()
return vnode && vnode.flags & VNodeFlags.COMPONENT_STATEFUL
? (vnode.children as ComponentInstance).$proxy
: null
2018-11-02 13:21:38 +08:00
} else {
return nextTick(() => {
return vnode && vnode.flags & VNodeFlags.COMPONENT_STATEFUL
? (vnode.children as ComponentInstance).$proxy
: null
})
}
2018-09-19 23:35:38 +08:00
}
return { render }
}
// Utils -----------------------------------------------------------------------
// retrieves a vnode from a children array, making sure to clone it if the
// vnode is already mounted.
function getNextVNode(ownerArray: VNode[], index: number): VNode {
const vnode = ownerArray[index]
return vnode.el === null ? vnode : (ownerArray[index] = cloneVNode(vnode))
}
// https://en.wikipedia.org/wiki/Longest_increasing_subsequence
function lis(arr: number[]): number[] {
const p = arr.slice()
const result = [0]
let i
let j
let u
let v
let c
const len = arr.length
for (i = 0; i < len; i++) {
const arrI = arr[i]
if (arrI !== 0) {
j = result[result.length - 1]
if (arr[j] < arrI) {
p[i] = j
result.push(i)
continue
}
u = 0
v = result.length - 1
while (u < v) {
c = ((u + v) / 2) | 0
if (arr[result[c]] < arrI) {
u = c + 1
} else {
v = c
}
}
if (arrI < arr[result[u]]) {
if (u > 0) {
p[i] = result[u - 1]
}
result[u] = i
}
}
}
u = result.length
v = result[u - 1]
while (u-- > 0) {
result[u] = v
v = p[v]
}
return result
}