-import { immutable, unwrap, lock, unlock } from '@vue/observer'
+import { immutable, unwrap } from '@vue/observer'
import { ComponentInstance } from './component'
import {
Data,
options: NormalizedPropsOptions | undefined,
data: Data | null
) {
- const [props, attrs] = resolveProps(data, options)
- instance.$props = immutable(props === EMPTY_OBJ ? {} : props)
+ const { 0: props, 1: attrs } = resolveProps(data, options)
+ instance.$props = __DEV__ ? immutable(props) : props
instance.$attrs = options
- ? immutable(attrs === EMPTY_OBJ ? {} : attrs)
+ ? __DEV__
+ ? immutable(attrs)
+ : attrs
: instance.$props
}
return [props, attrs]
}
-export function updateProps(
- instance: ComponentInstance,
- nextData: Data | null
-) {
- // instance.$props and instance.$attrs are observables that should not be
- // replaced. Instead, we mutate them to match latest props, which will trigger
- // updates if any value that's been used in child component has changed.
- const [nextProps, nextAttrs] = resolveProps(nextData, instance.$options.props)
- // unlock to temporarily allow mutatiing props
- unlock()
- const props = instance.$props
- const rawProps = unwrap(props)
- const hasEmptyProps = nextProps === EMPTY_OBJ
- for (const key in rawProps) {
- if (hasEmptyProps || !nextProps.hasOwnProperty(key)) {
- delete (props as any)[key]
- }
- }
- if (!hasEmptyProps) {
- for (const key in nextProps) {
- ;(props as any)[key] = nextProps[key]
- }
- }
- const attrs = instance.$attrs
- if (attrs !== props) {
- const rawAttrs = unwrap(attrs)
- const hasEmptyAttrs = nextAttrs === EMPTY_OBJ
- for (const key in rawAttrs) {
- if (hasEmptyAttrs || !nextAttrs.hasOwnProperty(key)) {
- delete attrs[key]
- }
- }
- if (!hasEmptyAttrs) {
- for (const key in nextAttrs) {
- attrs[key] = nextAttrs[key]
- }
- }
- }
- lock()
-}
-
export function normalizePropsOptions(
raw: ComponentPropsOptions | void
): NormalizedPropsOptions | void {
-import { autorun, stop } from '@vue/observer'
+import { autorun, stop, Autorun, immutable } from '@vue/observer'
import { queueJob } from '@vue/scheduler'
import { VNodeFlags, ChildrenFlags } from './flags'
import { EMPTY_OBJ, reservedPropRE, isString } from '@vue/shared'
Ref,
VNodeChildren
} from './vdom'
-import { ComponentInstance, FunctionalComponent } from './component'
-import { updateProps } from './componentProps'
+import { ComponentInstance } from './component'
import {
renderInstanceRoot,
renderFunctionalRoot,
createComponentInstance,
teardownComponentInstance,
- shouldUpdateFunctionalComponent
+ shouldUpdateComponent
} from './componentUtils'
import { KeepAliveSymbol } from './optional/keepAlive'
-import { pushWarningContext, popWarningContext } from './warning'
+import { pushWarningContext, popWarningContext, warn } from './warning'
import { handleError, ErrorTypes } from './errorHandling'
+import { resolveProps } from './componentProps'
export interface NodeOps {
createElement: (tag: string, isSVG?: boolean) => any
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
// single function so that we can create multiple renderes with different
// platform definitions. This allows for use cases like creating a test
isSVG: boolean,
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(
} else if (flags & VNodeFlags.COMPONENT_STATEFUL) {
patchStatefulComponent(prevVNode, nextVNode)
} else {
- patchFunctionalComponent(
- prevVNode,
- nextVNode,
- container,
- contextVNode,
- isSVG
- )
+ patchFunctionalComponent(prevVNode, nextVNode)
}
if (__DEV__) {
popWarningContext()
}
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 =
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) {
- 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()
} else if (instance.$vnode.flags & VNodeFlags.COMPONENT) {
instance.$vnode.contextVNode = nextVNode
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) {
// functional component returned another component
prevTree.contextVNode = nextVNode
// unmounting ----------------------------------------------------------------
function unmount(vnode: MountedVNode) {
- const { flags, data, children, childFlags, ref } = vnode
+ const { flags, data, children, childFlags, ref, handle } = vnode
const isElement = flags & VNodeFlags.ELEMENT
if (isElement || flags & VNodeFlags.FRAGMENT) {
if (isElement && data != null && data.vnodeBeforeUnmount) {
unmountComponentInstance(children as ComponentInstance)
}
} else {
+ // functional
+ stop((handle as FunctionalHandle).runner)
unmount(children as MountedVNode)
}
} else if (flags & VNodeFlags.PORTAL) {
beforeMount.call($proxy)
}
- const errorSchedulerHandler = (err: Error) => {
+ const handleSchedulerError = (err: Error) => {
handleError(err, instance, ErrorTypes.SCHEDULER)
}
const queueUpdate = (instance.$forceUpdate = () => {
- queueJob(instance._updateHandle, flushHooks, errorSchedulerHandler)
+ queueJob(instance._updateHandle, flushHooks, handleSchedulerError)
})
instance._updateHandle = autorun(
// to inject effects in first render
const { mounted } = instance.$options
if (mounted) {
- lifecycleHooks.push(() => {
+ lifecycleHooks.unshift(() => {
mounted.call($proxy)
})
}