]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor: separate vnode hooks and directive hooks
authorEvan You <yyx990803@gmail.com>
Wed, 18 Mar 2020 15:43:32 +0000 (11:43 -0400)
committerEvan You <yyx990803@gmail.com>
Wed, 18 Mar 2020 16:30:26 +0000 (12:30 -0400)
packages/runtime-core/src/component.ts
packages/runtime-core/src/componentProps.ts
packages/runtime-core/src/componentRenderUtils.ts
packages/runtime-core/src/directives.ts
packages/runtime-core/src/errorHandling.ts
packages/runtime-core/src/hydration.ts
packages/runtime-core/src/renderer.ts
packages/runtime-core/src/vnode.ts

index 470d4ec4dc5364dc4047a5276f4cd6af06b306aa..3dbfd0d4da76b908d8fbba430a68814d16c02461 100644 (file)
@@ -131,7 +131,6 @@ export interface ComponentInternalInstance {
   data: Data
   props: Data
   attrs: Data
-  vnodeHooks: Data
   slots: Slots
   proxy: ComponentPublicInstance | null
   // alternative proxy used only for runtime-compiled render functions using
@@ -204,7 +203,6 @@ export function createComponentInstance(
     data: EMPTY_OBJ,
     props: EMPTY_OBJ,
     attrs: EMPTY_OBJ,
-    vnodeHooks: EMPTY_OBJ,
     slots: EMPTY_OBJ,
     refs: EMPTY_OBJ,
 
index c243d5179eda18a16be25ad410deae1921446cbe..d2f35da2ee2b93376dab248dd13d8f7d4d7c5502 100644 (file)
@@ -106,7 +106,6 @@ export function resolveProps(
   const { 0: options, 1: needCastKeys } = normalizePropsOptions(_options)!
   const props: Data = {}
   let attrs: Data | undefined = undefined
-  let vnodeHooks: Data | undefined = undefined
 
   // update the instance propsProxy (passed to setup()) to trigger potential
   // changes
@@ -128,10 +127,6 @@ export function resolveProps(
       const value = rawProps[key]
       // key, ref are reserved and never passed down
       if (isReservedProp(key)) {
-        if (key !== 'key' && key !== 'ref') {
-          // vnode hooks.
-          ;(vnodeHooks || (vnodeHooks = {}))[key] = value
-        }
         continue
       }
       // prop option names are camelized during normalization, so to support
@@ -208,7 +203,6 @@ export function resolveProps(
 
   instance.props = props
   instance.attrs = attrs || EMPTY_OBJ
-  instance.vnodeHooks = vnodeHooks || EMPTY_OBJ
 }
 
 const normalizationMap = new WeakMap<
index 523565929352e5a326e8c62b9d5c62324686e733..316bb80ac478d7a8db1d7f2f201d173da0816eb7 100644 (file)
@@ -45,7 +45,6 @@ export function renderComponentRoot(
     props,
     slots,
     attrs,
-    vnodeHooks,
     emit,
     renderCache
   } = instance
@@ -104,10 +103,6 @@ export function renderComponentRoot(
       }
     }
 
-    // inherit vnode hooks
-    if (vnodeHooks !== EMPTY_OBJ) {
-      result = cloneVNode(result, vnodeHooks)
-    }
     // inherit scopeId
     const parentScopeId = parent && parent.type.__scopeId
     if (parentScopeId) {
index eb897c3552ee278ee673a28aaf239912d0a80ac1..434ab83f8a19dce275f80fd28cf0520d26b9b796 100644 (file)
@@ -11,8 +11,8 @@ return withDirectives(h(comp), [
 ])
 */
 
-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'
@@ -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
 export type DirectiveArguments = Array<
   | [Directive]
@@ -120,9 +90,7 @@ export function withDirectives<T extends VNode>(
     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)) {
@@ -131,36 +99,39 @@ export function withDirectives<T extends VNode>(
         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
+      ])
+    }
+  }
 }
index d9a0fc0b6e2670dd843c32eac518c5d7f6df205b..f027f0c3f76b147a725667640eb32ad0a9041154 100644 (file)
@@ -13,6 +13,7 @@ export const enum ErrorCodes {
   WATCH_CLEANUP,
   NATIVE_EVENT_HANDLER,
   COMPONENT_EVENT_HANDLER,
+  VNODE_HOOK,
   DIRECTIVE_HOOK,
   TRANSITION_HOOK,
   APP_ERROR_HANDLER,
@@ -42,6 +43,7 @@ export const ErrorTypeStrings: Record<number | string, string> = {
   [ErrorCodes.WATCH_CLEANUP]: 'watcher cleanup function',
   [ErrorCodes.NATIVE_EVENT_HANDLER]: 'native event handler',
   [ErrorCodes.COMPONENT_EVENT_HANDLER]: 'component event handler',
+  [ErrorCodes.VNODE_HOOK]: 'vnode hook',
   [ErrorCodes.DIRECTIVE_HOOK]: 'directive hook',
   [ErrorCodes.TRANSITION_HOOK]: 'transition hook',
   [ErrorCodes.APP_ERROR_HANDLER]: 'app errorHandler',
index 945fb8a3075d56d8b3fb3fb781cec398eab5bac0..d81e6abdf3c3d66d91c47b5c27c056fd1ccd5edf 100644 (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 { ComponentInternalInstance } from './component'
 import { invokeDirectiveHook } from './directives'
@@ -10,7 +18,7 @@ import {
   isOn,
   isString
 } from '@vue/shared'
-import { RendererInternals } from './renderer'
+import { RendererInternals, invokeVNodeHook } from './renderer'
 import {
   SuspenseImpl,
   SuspenseBoundary,
@@ -192,7 +200,7 @@ export function createHydrationFunctions(
     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
@@ -212,16 +220,23 @@ export function createHydrationFunctions(
           // 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 (
index 7849b941bc97ab75c216c16a931bb4e8fd2eba73..7b25f23dd79458d31148c2afadf71189d6cdf031 100644 (file)
@@ -51,7 +51,6 @@ import {
 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 {
@@ -62,8 +61,13 @@ 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__
 
@@ -526,7 +530,15 @@ function baseCreateRenderer<
   ) => {
     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 &&
@@ -546,9 +558,12 @@ function baseCreateRenderer<
           }
         }
         if ((vnodeHook = props.onVnodeBeforeMount) != null) {
-          invokeDirectiveHook(vnodeHook, parentComponent, vnode)
+          invokeVNodeHook(vnodeHook, parentComponent, vnode)
         }
       }
+      if (dirs != null) {
+        invokeDirectiveHook(vnode, null, parentComponent, 'beforeMount')
+      }
 
       // scopeId
       if (__BUNDLER__) {
@@ -585,11 +600,13 @@ function baseCreateRenderer<
     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)
     }
   }
@@ -630,13 +647,16 @@ function baseCreateRenderer<
     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) {
@@ -750,9 +770,10 @@ function baseCreateRenderer<
       )
     }
 
-    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)
     }
   }
@@ -1063,12 +1084,19 @@ function baseCreateRenderer<
     // 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,
@@ -1089,36 +1117,50 @@ function baseCreateRenderer<
           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
@@ -1136,7 +1178,7 @@ function baseCreateRenderer<
           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
@@ -1144,10 +1186,15 @@ function baseCreateRenderer<
           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()
         }
@@ -1617,7 +1664,8 @@ function baseCreateRenderer<
     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
@@ -1640,7 +1688,10 @@ function baseCreateRenderer<
     }
 
     if ((vnodeHook = props && props.onVnodeBeforeUnmount) != null) {
-      invokeDirectiveHook(vnodeHook, parentComponent, vnode)
+      invokeVNodeHook(vnodeHook, parentComponent, vnode)
+    }
+    if (shouldInvokeDirs) {
+      invokeDirectiveHook(vnode, null, parentComponent, 'beforeUnmount')
     }
 
     if (dynamicChildren != null) {
@@ -1654,9 +1705,14 @@ function baseCreateRenderer<
       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)
     }
   }
@@ -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
 function getSequence(arr: number[]): number[] {
   const p = arr.slice()
index ea0e1dbeff781bd26723bd87a52c262234fbab1e..592aa8b33cc6cf9b49806af2aff2e33a270bf632 100644 (file)
@@ -61,7 +61,11 @@ export type VNodeNormalizedRef = [ComponentInternalInstance, VNodeRef]
 
 type VNodeMountHook = (vnode: VNode) => void
 type VNodeUpdateHook = (vnode: VNode, oldVNode: VNode) => void
-export type VNodeHook = VNodeMountHook | VNodeUpdateHook
+export type VNodeHook =
+  | VNodeMountHook
+  | VNodeUpdateHook
+  | VNodeMountHook[]
+  | VNodeUpdateHook[]
 
 export interface VNodeProps {
   [key: string]: any
@@ -69,12 +73,12 @@ export interface VNodeProps {
   ref?: VNodeRef
 
   // vnode hooks
-  onVnodeBeforeMount?: VNodeMountHook
-  onVnodeMounted?: VNodeMountHook
-  onVnodeBeforeUpdate?: VNodeUpdateHook
-  onVnodeUpdated?: VNodeUpdateHook
-  onVnodeBeforeUnmount?: VNodeMountHook
-  onVnodeUnmounted?: VNodeMountHook
+  onVnodeBeforeMount?: VNodeMountHook | VNodeMountHook[]
+  onVnodeMounted?: VNodeMountHook | VNodeMountHook[]
+  onVnodeBeforeUpdate?: VNodeUpdateHook | VNodeUpdateHook[]
+  onVnodeUpdated?: VNodeUpdateHook | VNodeUpdateHook[]
+  onVnodeBeforeUnmount?: VNodeMountHook | VNodeMountHook[]
+  onVnodeUnmounted?: VNodeMountHook | VNodeMountHook[]
 }
 
 type VNodeChildAtom<HostNode, HostElement> =