]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat: vnode hooks
authorEvan You <yyx990803@gmail.com>
Tue, 25 Sep 2018 17:39:19 +0000 (13:39 -0400)
committerEvan You <yyx990803@gmail.com>
Tue, 25 Sep 2018 17:39:19 +0000 (13:39 -0400)
packages/core/src/componentProps.ts
packages/core/src/createRenderer.ts
packages/core/src/utils.ts
packages/core/src/vdom.ts

index 375a7155dbe2ec0db9da6d517f1f1266515dcb24..6686fb910a0a6c4f545f144983c9841dfb736256 100644 (file)
@@ -1,4 +1,4 @@
-import { EMPTY_OBJ, nativeOnRE } from './utils'
+import { EMPTY_OBJ, nativeOnRE, vnodeHookRE } from './utils'
 import {
   Component,
   ComponentClass,
@@ -92,14 +92,15 @@ export function resolveProps(
       if (key === 'key' || key === 'ref' || key === 'slots') {
         continue
       }
-      // class, style & nativeOn are always extracted into a separate `attrs`
-      // object, which can then be merged onto child component root.
-      // in addition, if the component has explicitly declared props, then
+      // class, style, nativeOn & directive hooks are always extracted into a
+      // separate `attrs` object, which can then be merged onto child component
+      // root. in addition, if the component has explicitly declared props, then
       // any non-matching props are extracted into `attrs` as well.
       let isNativeOn
       if (
         key === 'class' ||
         key === 'style' ||
+        vnodeHookRE.test(key) ||
         (isNativeOn = nativeOnRE.test(key)) ||
         (hasDeclaredProps && !options.hasOwnProperty(key))
       ) {
index cf27d189e4991102bbb7913cfe322ad41e45f385..b6ff8dfd84bde7af47fa9c6bb0d52b81509db9dd 100644 (file)
@@ -156,13 +156,14 @@ export function createRenderer(options: RendererOptions) {
     }
   }
 
-  // lifecycle hooks -----------------------------------------------------------
+  // lifecycle lifecycleHooks -----------------------------------------------------------
 
-  const hooks: Function[] = []
+  const lifecycleHooks: Function[] = []
+  const vnodeUpdatedHooks: Function[] = []
 
   function flushHooks() {
     let fn
-    while ((fn = hooks.shift())) {
+    while ((fn = lifecycleHooks.shift())) {
       fn()
     }
   }
@@ -222,6 +223,9 @@ export function createRenderer(options: RendererOptions) {
       for (const key in data) {
         patchData(el, key, null, data[key], null, vnode, isSVG)
       }
+      if (data.vnodeBeforeMount) {
+        data.vnodeBeforeMount(vnode)
+      }
     }
     if (childFlags !== ChildrenFlags.NO_CHILDREN) {
       const hasSVGChildren = isSVG && tag !== 'foreignObject'
@@ -243,11 +247,16 @@ export function createRenderer(options: RendererOptions) {
     if (ref) {
       mountRef(ref, el)
     }
+    if (data != null && data.vnodeMounted) {
+      lifecycleHooks.push(() => {
+        data.vnodeMounted(vnode)
+      })
+    }
     return el
   }
 
   function mountRef(ref: Ref, el: RenderNode | MountedComponent) {
-    hooks.push(() => {
+    lifecycleHooks.push(() => {
       ref(el)
     })
   }
@@ -438,10 +447,14 @@ export function createRenderer(options: RendererOptions) {
     }
 
     const el = (nextVNode.el = prevVNode.el) as RenderNode
-
-    // data
     const prevData = prevVNode.data
     const nextData = nextVNode.data
+
+    if (nextData != null && nextData.vnodeBeforeUpdate) {
+      nextData.vnodeBeforeUpdate(nextVNode, prevVNode)
+    }
+
+    // patch data
     if (prevData !== nextData) {
       const prevDataOrEmpty = prevData || EMPTY_OBJ
       const nextDataOrEmpty = nextData || EMPTY_OBJ
@@ -483,6 +496,12 @@ export function createRenderer(options: RendererOptions) {
       isSVG && nextVNode.tag !== 'foreignObject',
       null
     )
+
+    if (nextData != null && nextData.vnodeUpdated) {
+      vnodeUpdatedHooks.push(() => {
+        nextData.vnodeUpdated(nextVNode, prevVNode)
+      })
+    }
   }
 
   function patchComponent(
@@ -1064,12 +1083,19 @@ export function createRenderer(options: RendererOptions) {
   // unmounting ----------------------------------------------------------------
 
   function unmount(vnode: VNode) {
-    const { flags, children, childFlags, ref } = vnode
-    if (flags & VNodeFlags.ELEMENT || flags & VNodeFlags.FRAGMENT) {
+    const { flags, data, children, childFlags, ref } = vnode
+    const isElement = flags & VNodeFlags.ELEMENT
+    if (isElement || flags & VNodeFlags.FRAGMENT) {
+      if (isElement && data != null && data.vnodeBeforeUnmount) {
+        data.vnodeBeforeUnmount(vnode)
+      }
       unmountChildren(children as VNodeChildren, childFlags)
       if (teardownVNode !== void 0) {
         teardownVNode(vnode)
       }
+      if (isElement && data != null && data.vnodeUnmounted) {
+        data.vnodeUnmounted(vnode)
+      }
     } else if (flags & VNodeFlags.COMPONENT) {
       if (flags & VNodeFlags.COMPONENT_STATEFUL) {
         unmountComponentInstance(children as MountedComponent)
@@ -1187,7 +1213,7 @@ export function createRenderer(options: RendererOptions) {
       mountRef(ref, instance)
     }
     if (instance.mounted) {
-      hooks.push(() => {
+      lifecycleHooks.push(() => {
         ;(instance as any).mounted.call(instance.$proxy)
       })
     }
@@ -1227,10 +1253,20 @@ export function createRenderer(options: RendererOptions) {
       // will be added to the queue AFTER the parent's, but they should be
       // invoked BEFORE the parent's. Therefore we add them to the head of the
       // queue instead.
-      hooks.unshift(() => {
+      lifecycleHooks.unshift(() => {
         ;(instance as any).updated.call(instance.$proxy, nextVNode)
       })
     }
+
+    if (vnodeUpdatedHooks.length > 0) {
+      const vnodeUpdatedHooksForCurrentInstance = vnodeUpdatedHooks.slice()
+      vnodeUpdatedHooks.length = 0
+      lifecycleHooks.unshift(() => {
+        for (let i = 0; i < vnodeUpdatedHooksForCurrentInstance.length; i++) {
+          vnodeUpdatedHooksForCurrentInstance[i]()
+        }
+      })
+    }
   }
 
   function unmountComponentInstance(instance: MountedComponent) {
index e006fba836612acc33dbf3a0917550e71f231c96..60052e70f66769ce06820240add0bdb239ed4ee0 100644 (file)
@@ -4,7 +4,9 @@ export const NOOP = () => {}
 
 export const onRE = /^on/
 export const nativeOnRE = /^nativeOn/
-export const reservedPropRE = /^(?:key|ref|slots)$|^nativeOn/
+export const vnodeHookRE = /^vnode/
+export const handlersRE = /^on|^nativeOn|^vnode/
+export const reservedPropRE = /^(?:key|ref|slots)$|^nativeOn|^vnode/
 
 export function normalizeStyle(
   value: any
index c5ef61afaf017a214c290ece27ece87658241acf..e85d1b31f8c03a16db7b43c709fbc9441c139f1c 100644 (file)
@@ -5,7 +5,7 @@ import {
 } from './component'
 import { VNodeFlags, ChildrenFlags } from './flags'
 import { createComponentClassFromOptions } from './componentUtils'
-import { normalizeClass, normalizeStyle, onRE, nativeOnRE } from './utils'
+import { normalizeClass, normalizeStyle, handlersRE } from './utils'
 
 // Vue core is platform agnostic, so we are not using Element for "DOM" nodes.
 export interface RenderNode {
@@ -270,7 +270,8 @@ export function cloneVNode(vnode: VNode, extraData?: VNodeData): VNode {
           clonedData.class = normalizeClass([clonedData.class, extraData.class])
         } else if (key === 'style') {
           clonedData.style = normalizeStyle([clonedData.style, extraData.style])
-        } else if (onRE.test(key) || nativeOnRE.test(key)) {
+        } else if (handlersRE.test(key)) {
+          // on*, nativeOn*, vnode*
           const existing = clonedData[key]
           clonedData[key] = existing
             ? [].concat(existing, extraData[key])