refactor: separate vnode hooks and directive hooks

This commit is contained in:
Evan You 2020-03-18 11:43:32 -04:00
parent 6679799540
commit aa4ab39c1a
8 changed files with 164 additions and 117 deletions

View File

@ -131,7 +131,6 @@ export interface ComponentInternalInstance {
data: Data data: Data
props: Data props: Data
attrs: Data attrs: Data
vnodeHooks: Data
slots: Slots slots: Slots
proxy: ComponentPublicInstance | null proxy: ComponentPublicInstance | null
// alternative proxy used only for runtime-compiled render functions using // alternative proxy used only for runtime-compiled render functions using
@ -204,7 +203,6 @@ export function createComponentInstance(
data: EMPTY_OBJ, data: EMPTY_OBJ,
props: EMPTY_OBJ, props: EMPTY_OBJ,
attrs: EMPTY_OBJ, attrs: EMPTY_OBJ,
vnodeHooks: EMPTY_OBJ,
slots: EMPTY_OBJ, slots: EMPTY_OBJ,
refs: EMPTY_OBJ, refs: EMPTY_OBJ,

View File

@ -106,7 +106,6 @@ export function resolveProps(
const { 0: options, 1: needCastKeys } = normalizePropsOptions(_options)! const { 0: options, 1: needCastKeys } = normalizePropsOptions(_options)!
const props: Data = {} const props: Data = {}
let attrs: Data | undefined = undefined let attrs: Data | undefined = undefined
let vnodeHooks: Data | undefined = undefined
// update the instance propsProxy (passed to setup()) to trigger potential // update the instance propsProxy (passed to setup()) to trigger potential
// changes // changes
@ -128,10 +127,6 @@ export function resolveProps(
const value = rawProps[key] const value = rawProps[key]
// key, ref are reserved and never passed down // key, ref are reserved and never passed down
if (isReservedProp(key)) { if (isReservedProp(key)) {
if (key !== 'key' && key !== 'ref') {
// vnode hooks.
;(vnodeHooks || (vnodeHooks = {}))[key] = value
}
continue continue
} }
// prop option names are camelized during normalization, so to support // prop option names are camelized during normalization, so to support
@ -208,7 +203,6 @@ export function resolveProps(
instance.props = props instance.props = props
instance.attrs = attrs || EMPTY_OBJ instance.attrs = attrs || EMPTY_OBJ
instance.vnodeHooks = vnodeHooks || EMPTY_OBJ
} }
const normalizationMap = new WeakMap< const normalizationMap = new WeakMap<

View File

@ -45,7 +45,6 @@ export function renderComponentRoot(
props, props,
slots, slots,
attrs, attrs,
vnodeHooks,
emit, emit,
renderCache renderCache
} = instance } = instance
@ -104,10 +103,6 @@ export function renderComponentRoot(
} }
} }
// inherit vnode hooks
if (vnodeHooks !== EMPTY_OBJ) {
result = cloneVNode(result, vnodeHooks)
}
// inherit scopeId // inherit scopeId
const parentScopeId = parent && parent.type.__scopeId const parentScopeId = parent && parent.type.__scopeId
if (parentScopeId) { if (parentScopeId) {

View File

@ -11,8 +11,8 @@ return withDirectives(h(comp), [
]) ])
*/ */
import { VNode, VNodeHook } from './vnode' import { VNode } from './vnode'
import { isFunction, EMPTY_OBJ, makeMap, EMPTY_ARR } from '@vue/shared' import { isFunction, EMPTY_OBJ, makeMap } from '@vue/shared'
import { warn } from './warning' import { warn } from './warning'
import { ComponentInternalInstance, Data } from './component' import { ComponentInternalInstance, Data } from './component'
import { currentRenderingInstance } from './componentRenderUtils' import { currentRenderingInstance } from './componentRenderUtils'
@ -72,36 +72,6 @@ export function validateDirectiveName(name: string) {
} }
} }
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 // Directive, value, argument, modifiers
export type DirectiveArguments = Array< export type DirectiveArguments = Array<
| [Directive] | [Directive]
@ -120,9 +90,7 @@ export function withDirectives<T extends VNode>(
return vnode return vnode
} }
const instance = internalInstance.proxy const instance = internalInstance.proxy
const props = vnode.props || (vnode.props = {}) const bindings: DirectiveBinding[] = vnode.dirs || (vnode.dirs = [])
const bindings = vnode.dirs || (vnode.dirs = new Array(directives.length))
const injected: Record<string, true> = {}
for (let i = 0; i < directives.length; i++) { for (let i = 0; i < directives.length; i++) {
let [dir, value, arg, modifiers = EMPTY_OBJ] = directives[i] let [dir, value, arg, modifiers = EMPTY_OBJ] = directives[i]
if (isFunction(dir)) { if (isFunction(dir)) {
@ -131,36 +99,39 @@ export function withDirectives<T extends VNode>(
updated: dir updated: dir
} as ObjectDirective } as ObjectDirective
} }
bindings[i] = { bindings.push({
dir, dir,
instance, instance,
value, value,
oldValue: void 0, oldValue: void 0,
arg, arg,
modifiers 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 return vnode
} }
export function invokeDirectiveHook( export function invokeDirectiveHook(
hook: VNodeHook | VNodeHook[],
instance: ComponentInternalInstance | null,
vnode: VNode, vnode: VNode,
prevVNode: VNode | null = null prevVNode: VNode | null,
instance: ComponentInternalInstance | null,
name: keyof ObjectDirective
) { ) {
callWithAsyncErrorHandling(hook, instance, ErrorCodes.DIRECTIVE_HOOK, [ const bindings = vnode.dirs!
vnode, const oldBindings = prevVNode && prevVNode.dirs!
prevVNode 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
])
}
}
} }

View File

@ -13,6 +13,7 @@ export const enum ErrorCodes {
WATCH_CLEANUP, WATCH_CLEANUP,
NATIVE_EVENT_HANDLER, NATIVE_EVENT_HANDLER,
COMPONENT_EVENT_HANDLER, COMPONENT_EVENT_HANDLER,
VNODE_HOOK,
DIRECTIVE_HOOK, DIRECTIVE_HOOK,
TRANSITION_HOOK, TRANSITION_HOOK,
APP_ERROR_HANDLER, APP_ERROR_HANDLER,
@ -42,6 +43,7 @@ export const ErrorTypeStrings: Record<number | string, string> = {
[ErrorCodes.WATCH_CLEANUP]: 'watcher cleanup function', [ErrorCodes.WATCH_CLEANUP]: 'watcher cleanup function',
[ErrorCodes.NATIVE_EVENT_HANDLER]: 'native event handler', [ErrorCodes.NATIVE_EVENT_HANDLER]: 'native event handler',
[ErrorCodes.COMPONENT_EVENT_HANDLER]: 'component event handler', [ErrorCodes.COMPONENT_EVENT_HANDLER]: 'component event handler',
[ErrorCodes.VNODE_HOOK]: 'vnode hook',
[ErrorCodes.DIRECTIVE_HOOK]: 'directive hook', [ErrorCodes.DIRECTIVE_HOOK]: 'directive hook',
[ErrorCodes.TRANSITION_HOOK]: 'transition hook', [ErrorCodes.TRANSITION_HOOK]: 'transition hook',
[ErrorCodes.APP_ERROR_HANDLER]: 'app errorHandler', [ErrorCodes.APP_ERROR_HANDLER]: 'app errorHandler',

View File

@ -1,4 +1,12 @@
import { VNode, normalizeVNode, Text, Comment, Static, Fragment } from './vnode' import {
VNode,
normalizeVNode,
Text,
Comment,
Static,
Fragment,
VNodeHook
} from './vnode'
import { flushPostFlushCbs } from './scheduler' import { flushPostFlushCbs } from './scheduler'
import { ComponentInternalInstance } from './component' import { ComponentInternalInstance } from './component'
import { invokeDirectiveHook } from './directives' import { invokeDirectiveHook } from './directives'
@ -10,7 +18,7 @@ import {
isOn, isOn,
isString isString
} from '@vue/shared' } from '@vue/shared'
import { RendererInternals } from './renderer' import { RendererInternals, invokeVNodeHook } from './renderer'
import { import {
SuspenseImpl, SuspenseImpl,
SuspenseBoundary, SuspenseBoundary,
@ -192,7 +200,7 @@ export function createHydrationFunctions(
optimized: boolean optimized: boolean
) => { ) => {
optimized = optimized || vnode.dynamicChildren !== null 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 // skip props & children if this is hoisted static nodes
if (patchFlag !== PatchFlags.HOISTED) { if (patchFlag !== PatchFlags.HOISTED) {
// props // props
@ -212,16 +220,23 @@ export function createHydrationFunctions(
// iterating through props. // iterating through props.
patchProp(el, 'onClick', null, props.onClick) patchProp(el, 'onClick', null, props.onClick)
} }
// vnode hooks }
const { onVnodeBeforeMount, onVnodeMounted } = props // vnode / directive hooks
if (onVnodeBeforeMount != null) { let vnodeHooks: VNodeHook | null | undefined
invokeDirectiveHook(onVnodeBeforeMount, parentComponent, vnode) if ((vnodeHooks = props && props.onVnodeBeforeMount) != null) {
} invokeVNodeHook(vnodeHooks, parentComponent, vnode)
if (onVnodeMounted != null) { }
queueEffectWithSuspense(() => { if (dirs != null) {
invokeDirectiveHook(onVnodeMounted, parentComponent, vnode) invokeDirectiveHook(vnode, null, parentComponent, 'beforeMount')
}, parentSuspense) }
} if (
(vnodeHooks = props && props.onVnodeMounted) != null ||
dirs != null
) {
queueEffectWithSuspense(() => {
vnodeHooks && invokeVNodeHook(vnodeHooks, parentComponent, vnode)
dirs && invokeDirectiveHook(vnode, null, parentComponent, 'mounted')
}, parentSuspense)
} }
// children // children
if ( if (

View File

@ -51,7 +51,6 @@ import {
import { resolveProps } from './componentProps' import { resolveProps } from './componentProps'
import { resolveSlots } from './componentSlots' import { resolveSlots } from './componentSlots'
import { pushWarningContext, popWarningContext, warn } from './warning' import { pushWarningContext, popWarningContext, warn } from './warning'
import { invokeDirectiveHook } from './directives'
import { ComponentPublicInstance } from './componentProxy' import { ComponentPublicInstance } from './componentProxy'
import { createAppAPI, CreateAppFunction } from './apiCreateApp' import { createAppAPI, CreateAppFunction } from './apiCreateApp'
import { import {
@ -62,8 +61,13 @@ import {
import { PortalImpl } from './components/Portal' import { PortalImpl } from './components/Portal'
import { KeepAliveSink, isKeepAlive } from './components/KeepAlive' import { KeepAliveSink, isKeepAlive } from './components/KeepAlive'
import { registerHMR, unregisterHMR } from './hmr' import { registerHMR, unregisterHMR } from './hmr'
import { ErrorCodes, callWithErrorHandling } from './errorHandling' import {
ErrorCodes,
callWithErrorHandling,
callWithAsyncErrorHandling
} from './errorHandling'
import { createHydrationFunctions, RootHydrateFunction } from './hydration' import { createHydrationFunctions, RootHydrateFunction } from './hydration'
import { invokeDirectiveHook } from './directives'
const __HMR__ = __BUNDLER__ && __DEV__ const __HMR__ = __BUNDLER__ && __DEV__
@ -526,7 +530,15 @@ function baseCreateRenderer<
) => { ) => {
let el: HostElement let el: HostElement
let vnodeHook: VNodeHook | undefined | null let vnodeHook: VNodeHook | undefined | null
const { type, props, shapeFlag, transition, scopeId, patchFlag } = vnode const {
type,
props,
shapeFlag,
transition,
scopeId,
patchFlag,
dirs
} = vnode
if ( if (
vnode.el !== null && vnode.el !== null &&
hostCloneNode !== undefined && hostCloneNode !== undefined &&
@ -546,9 +558,12 @@ function baseCreateRenderer<
} }
} }
if ((vnodeHook = props.onVnodeBeforeMount) != null) { if ((vnodeHook = props.onVnodeBeforeMount) != null) {
invokeDirectiveHook(vnodeHook, parentComponent, vnode) invokeVNodeHook(vnodeHook, parentComponent, vnode)
} }
} }
if (dirs != null) {
invokeDirectiveHook(vnode, null, parentComponent, 'beforeMount')
}
// scopeId // scopeId
if (__BUNDLER__) { if (__BUNDLER__) {
@ -585,11 +600,13 @@ function baseCreateRenderer<
hostInsert(el, container, anchor) hostInsert(el, container, anchor)
if ( if (
(vnodeHook = props && props.onVnodeMounted) != null || (vnodeHook = props && props.onVnodeMounted) != null ||
(transition != null && !transition.persisted) (transition != null && !transition.persisted) ||
dirs != null
) { ) {
queuePostRenderEffect(() => { queuePostRenderEffect(() => {
vnodeHook && invokeDirectiveHook(vnodeHook, parentComponent, vnode) vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode)
transition && !transition.persisted && transition.enter(el) transition && !transition.persisted && transition.enter(el)
dirs && invokeDirectiveHook(vnode, null, parentComponent, 'mounted')
}, parentSuspense) }, parentSuspense)
} }
} }
@ -630,13 +647,16 @@ function baseCreateRenderer<
optimized: boolean optimized: boolean
) => { ) => {
const el = (n2.el = n1.el) as HostElement 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 oldProps = (n1 && n1.props) || EMPTY_OBJ
const newProps = n2.props || EMPTY_OBJ const newProps = n2.props || EMPTY_OBJ
let vnodeHook: VNodeHook | undefined | null let vnodeHook: VNodeHook | undefined | null
if ((vnodeHook = newProps.onVnodeBeforeUpdate) != 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 (__HMR__ && parentComponent && parentComponent.renderUpdated) {
@ -750,9 +770,10 @@ function baseCreateRenderer<
) )
} }
if ((vnodeHook = newProps.onVnodeUpdated) != null) { if ((vnodeHook = newProps.onVnodeUpdated) != null || dirs != null) {
queuePostRenderEffect(() => { queuePostRenderEffect(() => {
invokeDirectiveHook(vnodeHook!, parentComponent, n2, n1) vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, n2, n1)
dirs && invokeDirectiveHook(n2, n1, parentComponent, 'updated')
}, parentSuspense) }, parentSuspense)
} }
} }
@ -1063,12 +1084,19 @@ function baseCreateRenderer<
// create reactive effect for rendering // create reactive effect for rendering
instance.update = effect(function componentEffect() { instance.update = effect(function componentEffect() {
if (!instance.isMounted) { if (!instance.isMounted) {
let vnodeHook: VNodeHook | null | undefined
const { el, props } = initialVNode
const { bm, m, a, parent } = instance
const subTree = (instance.subTree = renderComponentRoot(instance)) const subTree = (instance.subTree = renderComponentRoot(instance))
// beforeMount hook // beforeMount hook
if (instance.bm !== null) { if (bm !== null) {
invokeHooks(instance.bm) 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. // vnode has adopted host node - perform hydration instead of mount.
hydrateNode( hydrateNode(
initialVNode.el as Node, initialVNode.el as Node,
@ -1089,36 +1117,50 @@ function baseCreateRenderer<
initialVNode.el = subTree.el initialVNode.el = subTree.el
} }
// mounted hook // mounted hook
if (instance.m !== null) { if (m !== null) {
queuePostRenderEffect(instance.m, parentSuspense) queuePostRenderEffect(m, parentSuspense)
}
// onVnodeMounted
if ((vnodeHook = props && props.onVnodeMounted) != null) {
queuePostRenderEffect(() => {
invokeVNodeHook(vnodeHook!, parent, initialVNode)
}, parentSuspense)
} }
// activated hook for keep-alive roots. // activated hook for keep-alive roots.
if ( if (
instance.a !== null && a !== null &&
instance.vnode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE initialVNode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
) { ) {
queuePostRenderEffect(instance.a, parentSuspense) queuePostRenderEffect(a, parentSuspense)
} }
instance.isMounted = true instance.isMounted = true
} else { } else {
// updateComponent // updateComponent
// This is triggered by mutation of component's own state (next: null) // This is triggered by mutation of component's own state (next: null)
// OR parent calling processComponent (next: HostVNode) // OR parent calling processComponent (next: HostVNode)
const { next } = instance let { next, bu, u, parent, vnode } = instance
let vnodeHook: VNodeHook | null | undefined
if (__DEV__) { if (__DEV__) {
pushWarningContext(next || instance.vnode) pushWarningContext(next || instance.vnode)
} }
if (next !== null) { if (next !== null) {
updateComponentPreRender(instance, next) updateComponentPreRender(instance, next)
} else {
next = vnode
} }
const nextTree = renderComponentRoot(instance) const nextTree = renderComponentRoot(instance)
const prevTree = instance.subTree const prevTree = instance.subTree
instance.subTree = nextTree instance.subTree = nextTree
// beforeUpdate hook // beforeUpdate hook
if (instance.bu !== null) { if (bu !== null) {
invokeHooks(instance.bu) invokeHooks(bu)
}
// onVnodeBeforeUpdate
if (
(vnodeHook = next.props && next.props.onVnodeBeforeUpdate) != null
) {
invokeVNodeHook(vnodeHook, parent, next, vnode)
} }
// reset refs // reset refs
// only needed if previous patch had refs // only needed if previous patch had refs
@ -1136,7 +1178,7 @@ function baseCreateRenderer<
parentSuspense, parentSuspense,
isSVG isSVG
) )
instance.vnode.el = nextTree.el next.el = nextTree.el
if (next === null) { if (next === null) {
// self-triggered update. In case of HOC, update parent component // self-triggered update. In case of HOC, update parent component
// vnode el. HOC is indicated by parent instance's subTree pointing // vnode el. HOC is indicated by parent instance's subTree pointing
@ -1144,10 +1186,15 @@ function baseCreateRenderer<
updateHOCHostEl(instance, nextTree.el) updateHOCHostEl(instance, nextTree.el)
} }
// updated hook // updated hook
if (instance.u !== null) { if (u !== null) {
queuePostRenderEffect(instance.u, parentSuspense) queuePostRenderEffect(u, parentSuspense)
}
// onVnodeUpdated
if ((vnodeHook = next.props && next.props.onVnodeUpdated) != null) {
queuePostRenderEffect(() => {
invokeVNodeHook(vnodeHook!, parent, next!, vnode)
}, parentSuspense)
} }
if (__DEV__) { if (__DEV__) {
popWarningContext() popWarningContext()
} }
@ -1617,7 +1664,8 @@ function baseCreateRenderer<
parentSuspense, parentSuspense,
doRemove = false 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 let vnodeHook: VNodeHook | undefined | null
// unset ref // unset ref
@ -1640,7 +1688,10 @@ function baseCreateRenderer<
} }
if ((vnodeHook = props && props.onVnodeBeforeUnmount) != null) { if ((vnodeHook = props && props.onVnodeBeforeUnmount) != null) {
invokeDirectiveHook(vnodeHook, parentComponent, vnode) invokeVNodeHook(vnodeHook, parentComponent, vnode)
}
if (shouldInvokeDirs) {
invokeDirectiveHook(vnode, null, parentComponent, 'beforeUnmount')
} }
if (dynamicChildren != null) { if (dynamicChildren != null) {
@ -1654,9 +1705,14 @@ function baseCreateRenderer<
remove(vnode) remove(vnode)
} }
if ((vnodeHook = props && props.onVnodeUnmounted) != null) { if (
(vnodeHook = props && props.onVnodeUnmounted) != null ||
shouldInvokeDirs
) {
queuePostRenderEffect(() => { queuePostRenderEffect(() => {
invokeDirectiveHook(vnodeHook!, parentComponent, vnode) vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode)
shouldInvokeDirs &&
invokeDirectiveHook(vnode, null, parentComponent, 'unmounted')
}, parentSuspense) }, parentSuspense)
} }
} }
@ -1880,6 +1936,18 @@ function baseCreateRenderer<
} }
} }
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 // https://en.wikipedia.org/wiki/Longest_increasing_subsequence
function getSequence(arr: number[]): number[] { function getSequence(arr: number[]): number[] {
const p = arr.slice() const p = arr.slice()

View File

@ -61,7 +61,11 @@ export type VNodeNormalizedRef = [ComponentInternalInstance, VNodeRef]
type VNodeMountHook = (vnode: VNode) => void type VNodeMountHook = (vnode: VNode) => void
type VNodeUpdateHook = (vnode: VNode, oldVNode: VNode) => void type VNodeUpdateHook = (vnode: VNode, oldVNode: VNode) => void
export type VNodeHook = VNodeMountHook | VNodeUpdateHook export type VNodeHook =
| VNodeMountHook
| VNodeUpdateHook
| VNodeMountHook[]
| VNodeUpdateHook[]
export interface VNodeProps { export interface VNodeProps {
[key: string]: any [key: string]: any
@ -69,12 +73,12 @@ export interface VNodeProps {
ref?: VNodeRef ref?: VNodeRef
// vnode hooks // vnode hooks
onVnodeBeforeMount?: VNodeMountHook onVnodeBeforeMount?: VNodeMountHook | VNodeMountHook[]
onVnodeMounted?: VNodeMountHook onVnodeMounted?: VNodeMountHook | VNodeMountHook[]
onVnodeBeforeUpdate?: VNodeUpdateHook onVnodeBeforeUpdate?: VNodeUpdateHook | VNodeUpdateHook[]
onVnodeUpdated?: VNodeUpdateHook onVnodeUpdated?: VNodeUpdateHook | VNodeUpdateHook[]
onVnodeBeforeUnmount?: VNodeMountHook onVnodeBeforeUnmount?: VNodeMountHook | VNodeMountHook[]
onVnodeUnmounted?: VNodeMountHook onVnodeUnmounted?: VNodeMountHook | VNodeMountHook[]
} }
type VNodeChildAtom<HostNode, HostElement> = type VNodeChildAtom<HostNode, HostElement> =