|
@@ -1,4 +1,4 @@
|
|
|
-import { autorun, stop } from '@vue/observer'
|
|
|
|
|
|
|
+import { autorun, stop, Autorun, immutable } from '@vue/observer'
|
|
|
import { queueJob } from '@vue/scheduler'
|
|
import { queueJob } from '@vue/scheduler'
|
|
|
import { VNodeFlags, ChildrenFlags } from './flags'
|
|
import { VNodeFlags, ChildrenFlags } from './flags'
|
|
|
import { EMPTY_OBJ, reservedPropRE, isString } from '@vue/shared'
|
|
import { EMPTY_OBJ, reservedPropRE, isString } from '@vue/shared'
|
|
@@ -10,18 +10,18 @@ import {
|
|
|
Ref,
|
|
Ref,
|
|
|
VNodeChildren
|
|
VNodeChildren
|
|
|
} from './vdom'
|
|
} from './vdom'
|
|
|
-import { ComponentInstance, FunctionalComponent } from './component'
|
|
|
|
|
-import { updateProps } from './componentProps'
|
|
|
|
|
|
|
+import { ComponentInstance } from './component'
|
|
|
import {
|
|
import {
|
|
|
renderInstanceRoot,
|
|
renderInstanceRoot,
|
|
|
renderFunctionalRoot,
|
|
renderFunctionalRoot,
|
|
|
createComponentInstance,
|
|
createComponentInstance,
|
|
|
teardownComponentInstance,
|
|
teardownComponentInstance,
|
|
|
- shouldUpdateFunctionalComponent
|
|
|
|
|
|
|
+ shouldUpdateComponent
|
|
|
} from './componentUtils'
|
|
} from './componentUtils'
|
|
|
import { KeepAliveSymbol } from './optional/keepAlive'
|
|
import { KeepAliveSymbol } from './optional/keepAlive'
|
|
|
-import { pushWarningContext, popWarningContext } from './warning'
|
|
|
|
|
|
|
+import { pushWarningContext, popWarningContext, warn } from './warning'
|
|
|
import { handleError, ErrorTypes } from './errorHandling'
|
|
import { handleError, ErrorTypes } from './errorHandling'
|
|
|
|
|
+import { resolveProps } from './componentProps'
|
|
|
|
|
|
|
|
export interface NodeOps {
|
|
export interface NodeOps {
|
|
|
createElement: (tag: string, isSVG?: boolean) => any
|
|
createElement: (tag: string, isSVG?: boolean) => any
|
|
@@ -57,6 +57,13 @@ export interface RendererOptions {
|
|
|
teardownVNode?: (vnode: VNode) => void
|
|
teardownVNode?: (vnode: VNode) => void
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+export interface FunctionalHandle {
|
|
|
|
|
+ current: VNode
|
|
|
|
|
+ prevTree: VNode
|
|
|
|
|
+ runner: Autorun
|
|
|
|
|
+ forceUpdate: () => void
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// The whole mounting / patching / unmouting logic is placed inside this
|
|
// The whole mounting / patching / unmouting logic is placed inside this
|
|
|
// single function so that we can create multiple renderes with different
|
|
// single function so that we can create multiple renderes with different
|
|
|
// platform definitions. This allows for use cases like creating a test
|
|
// platform definitions. This allows for use cases like creating a test
|
|
@@ -239,9 +246,64 @@ export function createRenderer(options: RendererOptions) {
|
|
|
isSVG: boolean,
|
|
isSVG: boolean,
|
|
|
endNode: RenderNode | null
|
|
endNode: RenderNode | null
|
|
|
) {
|
|
) {
|
|
|
- const subTree = (vnode.children = renderFunctionalRoot(vnode))
|
|
|
|
|
- mount(subTree, container, vnode as MountedVNode, isSVG, endNode)
|
|
|
|
|
- vnode.el = subTree.el as RenderNode
|
|
|
|
|
|
|
+ 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 = {
|
|
|
|
|
+ current: vnode,
|
|
|
|
|
+ prevTree: null as any,
|
|
|
|
|
+ runner: null as any,
|
|
|
|
|
+ forceUpdate: null as any
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const handleSchedulerError = (err: Error) => {
|
|
|
|
|
+ handleError(err, handle.current as VNode, ErrorTypes.SCHEDULER)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const queueUpdate = (handle.forceUpdate = () => {
|
|
|
|
|
+ queueJob(handle.runner, null, handleSchedulerError)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // we are using vnode.ref to store the functional component's update job
|
|
|
|
|
+ queueJob(
|
|
|
|
|
+ () => {
|
|
|
|
|
+ handle.runner = autorun(
|
|
|
|
|
+ () => {
|
|
|
|
|
+ if (handle.prevTree) {
|
|
|
|
|
+ // mounted
|
|
|
|
|
+ const { prevTree, current } = handle
|
|
|
|
|
+ const nextTree = (handle.prevTree = current.children = renderFunctionalRoot(
|
|
|
|
|
+ current
|
|
|
|
|
+ ))
|
|
|
|
|
+ patch(
|
|
|
|
|
+ prevTree as MountedVNode,
|
|
|
|
|
+ nextTree,
|
|
|
|
|
+ platformParentNode(current.el),
|
|
|
|
|
+ current as MountedVNode,
|
|
|
|
|
+ isSVG
|
|
|
|
|
+ )
|
|
|
|
|
+ current.el = nextTree.el
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // initial mount
|
|
|
|
|
+ const subTree = (handle.prevTree = vnode.children = renderFunctionalRoot(
|
|
|
|
|
+ vnode
|
|
|
|
|
+ ))
|
|
|
|
|
+ mount(subTree, container, vnode as MountedVNode, isSVG, endNode)
|
|
|
|
|
+ vnode.el = subTree.el as RenderNode
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ scheduler: queueUpdate
|
|
|
|
|
+ }
|
|
|
|
|
+ )
|
|
|
|
|
+ },
|
|
|
|
|
+ null,
|
|
|
|
|
+ handleSchedulerError
|
|
|
|
|
+ )
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function mountText(
|
|
function mountText(
|
|
@@ -462,13 +524,7 @@ export function createRenderer(options: RendererOptions) {
|
|
|
} else if (flags & VNodeFlags.COMPONENT_STATEFUL) {
|
|
} else if (flags & VNodeFlags.COMPONENT_STATEFUL) {
|
|
|
patchStatefulComponent(prevVNode, nextVNode)
|
|
patchStatefulComponent(prevVNode, nextVNode)
|
|
|
} else {
|
|
} else {
|
|
|
- patchFunctionalComponent(
|
|
|
|
|
- prevVNode,
|
|
|
|
|
- nextVNode,
|
|
|
|
|
- container,
|
|
|
|
|
- contextVNode,
|
|
|
|
|
- isSVG
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ patchFunctionalComponent(prevVNode, nextVNode)
|
|
|
}
|
|
}
|
|
|
if (__DEV__) {
|
|
if (__DEV__) {
|
|
|
popWarningContext()
|
|
popWarningContext()
|
|
@@ -476,31 +532,24 @@ export function createRenderer(options: RendererOptions) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function patchStatefulComponent(prevVNode: MountedVNode, nextVNode: VNode) {
|
|
function patchStatefulComponent(prevVNode: MountedVNode, nextVNode: VNode) {
|
|
|
- const { data: prevData, childFlags: prevChildFlags } = prevVNode
|
|
|
|
|
- const {
|
|
|
|
|
- data: nextData,
|
|
|
|
|
- slots: nextSlots,
|
|
|
|
|
- childFlags: nextChildFlags
|
|
|
|
|
- } = nextVNode
|
|
|
|
|
|
|
+ const { data: prevData } = prevVNode
|
|
|
|
|
+ const { data: nextData, slots: nextSlots } = nextVNode
|
|
|
|
|
|
|
|
const instance = (nextVNode.children =
|
|
const instance = (nextVNode.children =
|
|
|
prevVNode.children) as ComponentInstance
|
|
prevVNode.children) as ComponentInstance
|
|
|
- instance.$slots = nextSlots || EMPTY_OBJ
|
|
|
|
|
- instance.$parentVNode = nextVNode as MountedVNode
|
|
|
|
|
|
|
|
|
|
- // Update props. This will trigger child update if necessary.
|
|
|
|
|
if (nextData !== prevData) {
|
|
if (nextData !== prevData) {
|
|
|
- updateProps(instance, nextData)
|
|
|
|
|
|
|
+ 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
|
|
|
|
|
|
|
|
- // If has different slots content, or has non-compiled slots,
|
|
|
|
|
- // the child needs to be force updated. It's ok to call $forceUpdate
|
|
|
|
|
- // again even if props update has already queued an update, as the
|
|
|
|
|
- // scheduler will not queue the same update twice.
|
|
|
|
|
- const shouldForceUpdate =
|
|
|
|
|
- prevChildFlags !== nextChildFlags ||
|
|
|
|
|
- (nextChildFlags & ChildrenFlags.DYNAMIC_SLOTS) > 0
|
|
|
|
|
- if (shouldForceUpdate) {
|
|
|
|
|
|
|
+ if (shouldUpdateComponent(prevVNode, nextVNode)) {
|
|
|
instance.$forceUpdate()
|
|
instance.$forceUpdate()
|
|
|
} else if (instance.$vnode.flags & VNodeFlags.COMPONENT) {
|
|
} else if (instance.$vnode.flags & VNodeFlags.COMPONENT) {
|
|
|
instance.$vnode.contextVNode = nextVNode
|
|
instance.$vnode.contextVNode = nextVNode
|
|
@@ -508,28 +557,13 @@ export function createRenderer(options: RendererOptions) {
|
|
|
nextVNode.el = instance.$vnode.el
|
|
nextVNode.el = instance.$vnode.el
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- function patchFunctionalComponent(
|
|
|
|
|
- prevVNode: MountedVNode,
|
|
|
|
|
- nextVNode: VNode,
|
|
|
|
|
- container: RenderNode,
|
|
|
|
|
- contextVNode: MountedVNode | null,
|
|
|
|
|
- isSVG: boolean
|
|
|
|
|
- ) {
|
|
|
|
|
- // functional component tree is stored on the vnode as `children`
|
|
|
|
|
- const { data: prevData, slots: prevSlots } = prevVNode
|
|
|
|
|
- const { data: nextData, slots: nextSlots } = nextVNode
|
|
|
|
|
- const render = nextVNode.tag as FunctionalComponent
|
|
|
|
|
- const prevTree = prevVNode.children as MountedVNode
|
|
|
|
|
-
|
|
|
|
|
- let shouldUpdate = true
|
|
|
|
|
- if (render.pure && prevSlots == null && nextSlots == null) {
|
|
|
|
|
- shouldUpdate = shouldUpdateFunctionalComponent(prevData, nextData)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ function patchFunctionalComponent(prevVNode: MountedVNode, nextVNode: VNode) {
|
|
|
|
|
+ const prevTree = prevVNode.children as VNode
|
|
|
|
|
+ const handle = (nextVNode.handle = prevVNode.handle as FunctionalHandle)
|
|
|
|
|
+ handle.current = nextVNode
|
|
|
|
|
|
|
|
- if (shouldUpdate) {
|
|
|
|
|
- const nextTree = (nextVNode.children = renderFunctionalRoot(nextVNode))
|
|
|
|
|
- patch(prevTree, nextTree, container, nextVNode as MountedVNode, isSVG)
|
|
|
|
|
- nextVNode.el = nextTree.el
|
|
|
|
|
|
|
+ if (shouldUpdateComponent(prevVNode, nextVNode)) {
|
|
|
|
|
+ handle.forceUpdate()
|
|
|
} else if (prevTree.flags & VNodeFlags.COMPONENT) {
|
|
} else if (prevTree.flags & VNodeFlags.COMPONENT) {
|
|
|
// functional component returned another component
|
|
// functional component returned another component
|
|
|
prevTree.contextVNode = nextVNode
|
|
prevTree.contextVNode = nextVNode
|
|
@@ -1025,7 +1059,7 @@ export function createRenderer(options: RendererOptions) {
|
|
|
// unmounting ----------------------------------------------------------------
|
|
// unmounting ----------------------------------------------------------------
|
|
|
|
|
|
|
|
function unmount(vnode: MountedVNode) {
|
|
function unmount(vnode: MountedVNode) {
|
|
|
- const { flags, data, children, childFlags, ref } = vnode
|
|
|
|
|
|
|
+ const { flags, data, children, childFlags, ref, handle } = vnode
|
|
|
const isElement = flags & VNodeFlags.ELEMENT
|
|
const isElement = flags & VNodeFlags.ELEMENT
|
|
|
if (isElement || flags & VNodeFlags.FRAGMENT) {
|
|
if (isElement || flags & VNodeFlags.FRAGMENT) {
|
|
|
if (isElement && data != null && data.vnodeBeforeUnmount) {
|
|
if (isElement && data != null && data.vnodeBeforeUnmount) {
|
|
@@ -1046,6 +1080,8 @@ export function createRenderer(options: RendererOptions) {
|
|
|
unmountComponentInstance(children as ComponentInstance)
|
|
unmountComponentInstance(children as ComponentInstance)
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
|
|
|
+ // functional
|
|
|
|
|
+ stop((handle as FunctionalHandle).runner)
|
|
|
unmount(children as MountedVNode)
|
|
unmount(children as MountedVNode)
|
|
|
}
|
|
}
|
|
|
} else if (flags & VNodeFlags.PORTAL) {
|
|
} else if (flags & VNodeFlags.PORTAL) {
|
|
@@ -1144,12 +1180,12 @@ export function createRenderer(options: RendererOptions) {
|
|
|
beforeMount.call($proxy)
|
|
beforeMount.call($proxy)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const errorSchedulerHandler = (err: Error) => {
|
|
|
|
|
|
|
+ const handleSchedulerError = (err: Error) => {
|
|
|
handleError(err, instance, ErrorTypes.SCHEDULER)
|
|
handleError(err, instance, ErrorTypes.SCHEDULER)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const queueUpdate = (instance.$forceUpdate = () => {
|
|
const queueUpdate = (instance.$forceUpdate = () => {
|
|
|
- queueJob(instance._updateHandle, flushHooks, errorSchedulerHandler)
|
|
|
|
|
|
|
+ queueJob(instance._updateHandle, flushHooks, handleSchedulerError)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
instance._updateHandle = autorun(
|
|
instance._updateHandle = autorun(
|
|
@@ -1185,7 +1221,7 @@ export function createRenderer(options: RendererOptions) {
|
|
|
// to inject effects in first render
|
|
// to inject effects in first render
|
|
|
const { mounted } = instance.$options
|
|
const { mounted } = instance.$options
|
|
|
if (mounted) {
|
|
if (mounted) {
|
|
|
- lifecycleHooks.push(() => {
|
|
|
|
|
|
|
+ lifecycleHooks.unshift(() => {
|
|
|
mounted.call($proxy)
|
|
mounted.call($proxy)
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|