])
*/
-import { VNode, VNodeHook } from './vnode'
-import { isFunction, EMPTY_OBJ, makeMap, EMPTY_ARR } from '@vue/shared'
+import { VNode } from './vnode'
+import { isFunction, EMPTY_OBJ, makeMap } from '@vue/shared'
import { warn } from './warning'
import { ComponentInternalInstance, Data } from './component'
import { currentRenderingInstance } from './componentRenderUtils'
}
}
-const directiveToVnodeHooksMap = /*#__PURE__*/ [
- 'beforeMount',
- 'mounted',
- 'beforeUpdate',
- 'updated',
- 'beforeUnmount',
- 'unmounted'
-].reduce(
- (map, key: keyof ObjectDirective) => {
- const vnodeKey = `onVnode` + key[0].toUpperCase() + key.slice(1)
- const vnodeHook = (vnode: VNode, prevVnode: VNode | null) => {
- const bindings = vnode.dirs!
- const prevBindings = prevVnode ? prevVnode.dirs! : EMPTY_ARR
- for (let i = 0; i < bindings.length; i++) {
- const binding = bindings[i]
- const hook = binding.dir[key] as DirectiveHook
- if (hook != null) {
- if (prevVnode != null) {
- binding.oldValue = prevBindings[i].value
- }
- hook(vnode.el, binding, vnode, prevVnode)
- }
- }
- }
- map[key] = [vnodeKey, vnodeHook]
- return map
- },
- {} as Record<string, [string, Function]>
-)
-
// Directive, value, argument, modifiers
export type DirectiveArguments = Array<
| [Directive]
return vnode
}
const instance = internalInstance.proxy
- const props = vnode.props || (vnode.props = {})
- const bindings = vnode.dirs || (vnode.dirs = new Array(directives.length))
- const injected: Record<string, true> = {}
+ const bindings: DirectiveBinding[] = vnode.dirs || (vnode.dirs = [])
for (let i = 0; i < directives.length; i++) {
let [dir, value, arg, modifiers = EMPTY_OBJ] = directives[i]
if (isFunction(dir)) {
updated: dir
} as ObjectDirective
}
- bindings[i] = {
+ bindings.push({
dir,
instance,
value,
oldValue: void 0,
arg,
modifiers
- }
- // inject onVnodeXXX hooks
- for (const key in dir) {
- const mapped = directiveToVnodeHooksMap[key]
- if (mapped && !injected[key]) {
- const { 0: hookName, 1: hook } = mapped
- const existing = props[hookName]
- props[hookName] = existing ? [].concat(existing, hook as any) : hook
- injected[key] = true
- }
- }
+ })
}
return vnode
}
export function invokeDirectiveHook(
- hook: VNodeHook | VNodeHook[],
- instance: ComponentInternalInstance | null,
vnode: VNode,
- prevVNode: VNode | null = null
+ prevVNode: VNode | null,
+ instance: ComponentInternalInstance | null,
+ name: keyof ObjectDirective
) {
- callWithAsyncErrorHandling(hook, instance, ErrorCodes.DIRECTIVE_HOOK, [
- vnode,
- prevVNode
- ])
+ const bindings = vnode.dirs!
+ const oldBindings = prevVNode && prevVNode.dirs!
+ for (let i = 0; i < bindings.length; i++) {
+ const binding = bindings[i]
+ if (oldBindings) {
+ binding.oldValue = oldBindings[i].value
+ }
+ const hook = binding.dir[name] as DirectiveHook | undefined
+ if (hook) {
+ callWithAsyncErrorHandling(hook, instance, ErrorCodes.DIRECTIVE_HOOK, [
+ vnode.el,
+ binding,
+ vnode,
+ prevVNode
+ ])
+ }
+ }
}
-import { VNode, normalizeVNode, Text, Comment, Static, Fragment } from './vnode'
+import {
+ VNode,
+ normalizeVNode,
+ Text,
+ Comment,
+ Static,
+ Fragment,
+ VNodeHook
+} from './vnode'
import { flushPostFlushCbs } from './scheduler'
import { ComponentInternalInstance } from './component'
import { invokeDirectiveHook } from './directives'
isOn,
isString
} from '@vue/shared'
-import { RendererInternals } from './renderer'
+import { RendererInternals, invokeVNodeHook } from './renderer'
import {
SuspenseImpl,
SuspenseBoundary,
optimized: boolean
) => {
optimized = optimized || vnode.dynamicChildren !== null
- const { props, patchFlag, shapeFlag } = vnode
+ const { props, patchFlag, shapeFlag, dirs } = vnode
// skip props & children if this is hoisted static nodes
if (patchFlag !== PatchFlags.HOISTED) {
// props
// iterating through props.
patchProp(el, 'onClick', null, props.onClick)
}
- // vnode hooks
- const { onVnodeBeforeMount, onVnodeMounted } = props
- if (onVnodeBeforeMount != null) {
- invokeDirectiveHook(onVnodeBeforeMount, parentComponent, vnode)
- }
- if (onVnodeMounted != null) {
- queueEffectWithSuspense(() => {
- invokeDirectiveHook(onVnodeMounted, parentComponent, vnode)
- }, parentSuspense)
- }
+ }
+ // vnode / directive hooks
+ let vnodeHooks: VNodeHook | null | undefined
+ if ((vnodeHooks = props && props.onVnodeBeforeMount) != null) {
+ invokeVNodeHook(vnodeHooks, parentComponent, vnode)
+ }
+ if (dirs != null) {
+ invokeDirectiveHook(vnode, null, parentComponent, 'beforeMount')
+ }
+ if (
+ (vnodeHooks = props && props.onVnodeMounted) != null ||
+ dirs != null
+ ) {
+ queueEffectWithSuspense(() => {
+ vnodeHooks && invokeVNodeHook(vnodeHooks, parentComponent, vnode)
+ dirs && invokeDirectiveHook(vnode, null, parentComponent, 'mounted')
+ }, parentSuspense)
}
// children
if (
import { resolveProps } from './componentProps'
import { resolveSlots } from './componentSlots'
import { pushWarningContext, popWarningContext, warn } from './warning'
-import { invokeDirectiveHook } from './directives'
import { ComponentPublicInstance } from './componentProxy'
import { createAppAPI, CreateAppFunction } from './apiCreateApp'
import {
import { PortalImpl } from './components/Portal'
import { KeepAliveSink, isKeepAlive } from './components/KeepAlive'
import { registerHMR, unregisterHMR } from './hmr'
-import { ErrorCodes, callWithErrorHandling } from './errorHandling'
+import {
+ ErrorCodes,
+ callWithErrorHandling,
+ callWithAsyncErrorHandling
+} from './errorHandling'
import { createHydrationFunctions, RootHydrateFunction } from './hydration'
+import { invokeDirectiveHook } from './directives'
const __HMR__ = __BUNDLER__ && __DEV__
) => {
let el: HostElement
let vnodeHook: VNodeHook | undefined | null
- const { type, props, shapeFlag, transition, scopeId, patchFlag } = vnode
+ const {
+ type,
+ props,
+ shapeFlag,
+ transition,
+ scopeId,
+ patchFlag,
+ dirs
+ } = vnode
if (
vnode.el !== null &&
hostCloneNode !== undefined &&
}
}
if ((vnodeHook = props.onVnodeBeforeMount) != null) {
- invokeDirectiveHook(vnodeHook, parentComponent, vnode)
+ invokeVNodeHook(vnodeHook, parentComponent, vnode)
}
}
+ if (dirs != null) {
+ invokeDirectiveHook(vnode, null, parentComponent, 'beforeMount')
+ }
// scopeId
if (__BUNDLER__) {
hostInsert(el, container, anchor)
if (
(vnodeHook = props && props.onVnodeMounted) != null ||
- (transition != null && !transition.persisted)
+ (transition != null && !transition.persisted) ||
+ dirs != null
) {
queuePostRenderEffect(() => {
- vnodeHook && invokeDirectiveHook(vnodeHook, parentComponent, vnode)
+ vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode)
transition && !transition.persisted && transition.enter(el)
+ dirs && invokeDirectiveHook(vnode, null, parentComponent, 'mounted')
}, parentSuspense)
}
}
optimized: boolean
) => {
const el = (n2.el = n1.el) as HostElement
- let { patchFlag, dynamicChildren } = n2
+ let { patchFlag, dynamicChildren, dirs } = n2
const oldProps = (n1 && n1.props) || EMPTY_OBJ
const newProps = n2.props || EMPTY_OBJ
let vnodeHook: VNodeHook | undefined | null
if ((vnodeHook = newProps.onVnodeBeforeUpdate) != null) {
- invokeDirectiveHook(vnodeHook, parentComponent, n2, n1)
+ invokeVNodeHook(vnodeHook, parentComponent, n2, n1)
+ }
+ if (dirs != null) {
+ invokeDirectiveHook(n2, n1, parentComponent, 'beforeUpdate')
}
if (__HMR__ && parentComponent && parentComponent.renderUpdated) {
)
}
- if ((vnodeHook = newProps.onVnodeUpdated) != null) {
+ if ((vnodeHook = newProps.onVnodeUpdated) != null || dirs != null) {
queuePostRenderEffect(() => {
- invokeDirectiveHook(vnodeHook!, parentComponent, n2, n1)
+ vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, n2, n1)
+ dirs && invokeDirectiveHook(n2, n1, parentComponent, 'updated')
}, parentSuspense)
}
}
// create reactive effect for rendering
instance.update = effect(function componentEffect() {
if (!instance.isMounted) {
+ let vnodeHook: VNodeHook | null | undefined
+ const { el, props } = initialVNode
+ const { bm, m, a, parent } = instance
const subTree = (instance.subTree = renderComponentRoot(instance))
// beforeMount hook
- if (instance.bm !== null) {
- invokeHooks(instance.bm)
+ if (bm !== null) {
+ invokeHooks(bm)
}
- if (initialVNode.el && hydrateNode) {
+ // onVnodeBeforeMount
+ if ((vnodeHook = props && props.onVnodeBeforeMount) != null) {
+ invokeVNodeHook(vnodeHook, parent, initialVNode)
+ }
+ if (el && hydrateNode) {
// vnode has adopted host node - perform hydration instead of mount.
hydrateNode(
initialVNode.el as Node,
initialVNode.el = subTree.el
}
// mounted hook
- if (instance.m !== null) {
- queuePostRenderEffect(instance.m, parentSuspense)
+ if (m !== null) {
+ queuePostRenderEffect(m, parentSuspense)
+ }
+ // onVnodeMounted
+ if ((vnodeHook = props && props.onVnodeMounted) != null) {
+ queuePostRenderEffect(() => {
+ invokeVNodeHook(vnodeHook!, parent, initialVNode)
+ }, parentSuspense)
}
// activated hook for keep-alive roots.
if (
- instance.a !== null &&
- instance.vnode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
+ a !== null &&
+ initialVNode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
) {
- queuePostRenderEffect(instance.a, parentSuspense)
+ queuePostRenderEffect(a, parentSuspense)
}
instance.isMounted = true
} else {
// updateComponent
// This is triggered by mutation of component's own state (next: null)
// OR parent calling processComponent (next: HostVNode)
- const { next } = instance
-
+ let { next, bu, u, parent, vnode } = instance
+ let vnodeHook: VNodeHook | null | undefined
if (__DEV__) {
pushWarningContext(next || instance.vnode)
}
if (next !== null) {
updateComponentPreRender(instance, next)
+ } else {
+ next = vnode
}
const nextTree = renderComponentRoot(instance)
const prevTree = instance.subTree
instance.subTree = nextTree
// beforeUpdate hook
- if (instance.bu !== null) {
- invokeHooks(instance.bu)
+ if (bu !== null) {
+ invokeHooks(bu)
+ }
+ // onVnodeBeforeUpdate
+ if (
+ (vnodeHook = next.props && next.props.onVnodeBeforeUpdate) != null
+ ) {
+ invokeVNodeHook(vnodeHook, parent, next, vnode)
}
// reset refs
// only needed if previous patch had refs
parentSuspense,
isSVG
)
- instance.vnode.el = nextTree.el
+ next.el = nextTree.el
if (next === null) {
// self-triggered update. In case of HOC, update parent component
// vnode el. HOC is indicated by parent instance's subTree pointing
updateHOCHostEl(instance, nextTree.el)
}
// updated hook
- if (instance.u !== null) {
- queuePostRenderEffect(instance.u, parentSuspense)
+ if (u !== null) {
+ queuePostRenderEffect(u, parentSuspense)
+ }
+ // onVnodeUpdated
+ if ((vnodeHook = next.props && next.props.onVnodeUpdated) != null) {
+ queuePostRenderEffect(() => {
+ invokeVNodeHook(vnodeHook!, parent, next!, vnode)
+ }, parentSuspense)
}
-
if (__DEV__) {
popWarningContext()
}
parentSuspense,
doRemove = false
) => {
- const { props, ref, children, dynamicChildren, shapeFlag } = vnode
+ const { props, ref, children, dynamicChildren, shapeFlag, dirs } = vnode
+ const shouldInvokeDirs = dirs != null && shapeFlag & ShapeFlags.ELEMENT
let vnodeHook: VNodeHook | undefined | null
// unset ref
}
if ((vnodeHook = props && props.onVnodeBeforeUnmount) != null) {
- invokeDirectiveHook(vnodeHook, parentComponent, vnode)
+ invokeVNodeHook(vnodeHook, parentComponent, vnode)
+ }
+ if (shouldInvokeDirs) {
+ invokeDirectiveHook(vnode, null, parentComponent, 'beforeUnmount')
}
if (dynamicChildren != null) {
remove(vnode)
}
- if ((vnodeHook = props && props.onVnodeUnmounted) != null) {
+ if (
+ (vnodeHook = props && props.onVnodeUnmounted) != null ||
+ shouldInvokeDirs
+ ) {
queuePostRenderEffect(() => {
- invokeDirectiveHook(vnodeHook!, parentComponent, vnode)
+ vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode)
+ shouldInvokeDirs &&
+ invokeDirectiveHook(vnode, null, parentComponent, 'unmounted')
}, parentSuspense)
}
}
}
}
+export function invokeVNodeHook(
+ hook: VNodeHook,
+ instance: ComponentInternalInstance | null,
+ vnode: VNode,
+ prevVNode: VNode | null = null
+) {
+ callWithAsyncErrorHandling(hook, instance, ErrorCodes.VNODE_HOOK, [
+ vnode,
+ prevVNode
+ ])
+}
+
// https://en.wikipedia.org/wiki/Longest_increasing_subsequence
function getSequence(arr: number[]): number[] {
const p = arr.slice()