diff --git a/packages/core/src/component.ts b/packages/core/src/component.ts index 2995956f..4a1f7ad9 100644 --- a/packages/core/src/component.ts +++ b/packages/core/src/component.ts @@ -54,6 +54,8 @@ export interface MountedComponent 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 diff --git a/packages/core/src/componentUtils.ts b/packages/core/src/componentUtils.ts index 1630e8ae..c401ad20 100644 --- a/packages/core/src/componentUtils.ts +++ b/packages/core/src/componentUtils.ts @@ -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) { diff --git a/packages/core/src/createRenderer.ts b/packages/core/src/createRenderer.ts index a33f5f44..87b8c6c0 100644 --- a/packages/core/src/createRenderer.ts +++ b/packages/core/src/createRenderer.ts @@ -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 ----------------------------------------------------------------------- diff --git a/packages/core/src/h.ts b/packages/core/src/h.ts index e07fbc66..993a418a 100644 --- a/packages/core/src/h.ts +++ b/packages/core/src/h.ts @@ -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, diff --git a/packages/core/src/optional/keepAlive.ts b/packages/core/src/optional/keepAlive.ts index cb045cfc..29386e7c 100644 --- a/packages/core/src/optional/keepAlive.ts +++ b/packages/core/src/optional/keepAlive.ts @@ -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 }