]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat: activated/deactivated hooks
authorEvan You <yyx990803@gmail.com>
Wed, 26 Sep 2018 22:34:21 +0000 (18:34 -0400)
committerEvan You <yyx990803@gmail.com>
Wed, 26 Sep 2018 22:34:21 +0000 (18:34 -0400)
packages/core/src/component.ts
packages/core/src/componentUtils.ts
packages/core/src/createRenderer.ts
packages/core/src/h.ts
packages/core/src/optional/keepAlive.ts

index 2995956f01daa5387891876276eba46bf216b60a..4a1f7ad9138addd00c6384a50da957bc27f260a0 100644 (file)
@@ -54,6 +54,8 @@ export interface MountedComponent<D = Data, P = Data>
   beforeUnmount?(): void
   unmounted?(): void
   errorCaptured?(): (err: Error, type: ErrorTypes) => boolean | void
+  activated?(): void
+  deactivated?(): void
 
   _updateHandle: Autorun
   _queueJob: ((fn: () => void) => void)
@@ -95,6 +97,7 @@ class InternalComponent {
   public _queueJob: ((fn: () => void) => void) | null = null
   public _revokeProxy: () => void
   public _isVue: boolean = true
+  public _inactiveRoot: boolean = false
 
   constructor(options?: ComponentOptions) {
     this.$options = options || (this.constructor as any).options || EMPTY_OBJ
index 1630e8ae93746250fa8692f0eede3cc88f95bc64..c401ad2011c1fecbd0e7767699816b842837cb36 100644 (file)
@@ -122,17 +122,13 @@ export function normalizeComponentRoot(
       componentVNode &&
       (flags & VNodeFlags.COMPONENT || flags & VNodeFlags.ELEMENT)
     ) {
-      const isKeepAlive = (flags & VNodeFlags.COMPONENT_STATEFUL_KEPT_ALIVE) > 0
       if (
         inheritAttrs !== false &&
         attrs !== void 0 &&
         Object.keys(attrs).length > 0
       ) {
         vnode = cloneVNode(vnode, attrs)
-        if (isKeepAlive) {
-          vnode.el = el
-        }
-      } else if (el && !isKeepAlive) {
+      } else if (el) {
         vnode = cloneVNode(vnode)
       }
       if (flags & VNodeFlags.COMPONENT) {
index a33f5f441d71dbbac955a6b5801a083d657e7472..87b8c6c08eaa2bb953e19509da4536414423faa3 100644 (file)
@@ -274,8 +274,7 @@ export function createRenderer(options: RendererOptions) {
     if (flags & VNodeFlags.COMPONENT_STATEFUL) {
       if (flags & VNodeFlags.COMPONENT_STATEFUL_KEPT_ALIVE) {
         // kept-alive
-        el = vnode.el as RenderNode
-        // TODO activated hook
+        el = activateComponentInstance(vnode)
       } else {
         el = mountComponentInstance(
           vnode,
@@ -287,7 +286,6 @@ export function createRenderer(options: RendererOptions) {
         )
       }
     } else {
-      debugger
       // functional component
       const render = tag as FunctionalComponent
       const { props, attrs } = resolveProps(data, render.props, render)
@@ -1106,7 +1104,9 @@ export function createRenderer(options: RendererOptions) {
       }
     } else if (flags & VNodeFlags.COMPONENT) {
       if (flags & VNodeFlags.COMPONENT_STATEFUL) {
-        if ((flags & VNodeFlags.COMPONENT_STATEFUL_SHOULD_KEEP_ALIVE) === 0) {
+        if (flags & VNodeFlags.COMPONENT_STATEFUL_SHOULD_KEEP_ALIVE) {
+          deactivateComponentInstance(children as MountedComponent)
+        } else {
           unmountComponentInstance(children as MountedComponent)
         }
       } else {
@@ -1303,6 +1303,63 @@ export function createRenderer(options: RendererOptions) {
     }
   }
 
+  // Keep Alive ----------------------------------------------------------------
+
+  function activateComponentInstance(vnode: VNode): RenderNode {
+    const instance = vnode.children as MountedComponent
+    const el = (vnode.el = instance.$el)
+    lifecycleHooks.push(() => {
+      callActivatedHook(instance, true)
+    })
+    return el as RenderNode
+  }
+
+  function callActivatedHook(instance: MountedComponent, asRoot: boolean) {
+    // 1. check if we are inside an inactive parent tree.
+    if (asRoot) {
+      instance._inactiveRoot = false
+      if (isInInactiveTree(instance)) return
+    }
+    if (asRoot || !instance._inactiveRoot) {
+      // 2. recursively call activated on child tree, depth-first
+      const { $children } = instance
+      for (let i = 0; i < $children.length; i++) {
+        callActivatedHook($children[i], false)
+      }
+      if (instance.activated) {
+        instance.activated.call(instance.$proxy)
+      }
+    }
+  }
+
+  function deactivateComponentInstance(instance: MountedComponent) {
+    callDeactivateHook(instance, true)
+  }
+
+  function callDeactivateHook(instance: MountedComponent, asRoot: boolean) {
+    if (asRoot) {
+      instance._inactiveRoot = true
+      if (isInInactiveTree(instance)) return
+    }
+    if (asRoot || !instance._inactiveRoot) {
+      // 2. recursively call deactivated on child tree, depth-first
+      const { $children } = instance
+      for (let i = 0; i < $children.length; i++) {
+        callDeactivateHook($children[i], false)
+      }
+      if (instance.deactivated) {
+        instance.deactivated.call(instance.$proxy)
+      }
+    }
+  }
+
+  function isInInactiveTree(instance: MountedComponent): boolean {
+    while ((instance = instance.$parent as any) !== null) {
+      if (instance._inactiveRoot) return true
+    }
+    return false
+  }
+
   // TODO hydrating ------------------------------------------------------------
 
   // API -----------------------------------------------------------------------
index e07fbc669e071e205b98a9c9e5ebd317343fa0df..993a418a5d3dde014055ebf85a49baf30e7ad756 100644 (file)
@@ -80,12 +80,14 @@ export const h = ((tag: ElementType, data?: any, children?: any): VNode => {
     )
   } else if (tag === Fragment) {
     if (__DEV__ && ref) {
-      // TODO warn fragment cannot have ref
+      console.warn(
+        'Ref cannot be used on Fragments. Use it on inner elements instead.'
+      )
     }
     return createFragment(children, ChildrenFlags.UNKNOWN_CHILDREN, key)
   } else if (tag === Portal) {
     if (__DEV__ && !portalTarget) {
-      // TODO warn portal must have a target
+      console.warn('Portal must have a target: ', portalTarget)
     }
     return createPortal(
       portalTarget,
@@ -95,6 +97,12 @@ export const h = ((tag: ElementType, data?: any, children?: any): VNode => {
       ref
     )
   } else {
+    if (
+      __DEV__ &&
+      (!tag || (typeof tag !== 'function' && typeof tag !== 'object'))
+    ) {
+      console.warn('Invalid component passed to h(): ', tag)
+    }
     // component
     return createComponentVNode(
       tag,
index cb045cfcb33e96ae6b33dd00b7543a9fa0a8355a..29386e7c32dd546fe4c5706eae077459e4e8c748 100644 (file)
@@ -29,7 +29,7 @@ export class KeepAlive extends Component<{}, KeepAliveProps> {
     this.keys = new Set()
   }
 
-  unmounted() {
+  beforeUnmount() {
     this.cache.forEach(vnode => {
       // change flag so it can be properly unmounted
       vnode.flags = VNodeFlags.COMPONENT_STATEFUL_NORMAL
@@ -88,21 +88,23 @@ export class KeepAlive extends Component<{}, KeepAliveProps> {
     const { cache, keys } = this
     const key = vnode.key == null ? comp : vnode.key
     const cached = cache.get(key)
+    cache.set(key, vnode)
+
     if (cached) {
       vnode.children = cached.children
-      vnode.el = cached.el
+      // avoid vnode being mounted as fresh
       vnode.flags |= VNodeFlags.COMPONENT_STATEFUL_KEPT_ALIVE
       // make this key the freshest
       keys.delete(key)
       keys.add(key)
     } else {
-      cache.set(key, vnode)
       keys.add(key)
       // prune oldest entry
       if (max && keys.size > parseInt(max, 10)) {
         this.pruneCacheEntry(Array.from(this.keys)[0])
       }
     }
+    // avoid vnode being unmounted
     vnode.flags |= VNodeFlags.COMPONENT_STATEFUL_SHOULD_KEEP_ALIVE
     return vnode
   }