feat: activated/deactivated hooks

This commit is contained in:
Evan You 2018-09-26 18:34:21 -04:00
parent 7c2ec8ace0
commit ee50fb9723
5 changed files with 80 additions and 14 deletions

View File

@ -54,6 +54,8 @@ export interface MountedComponent<D = Data, P = Data>
beforeUnmount?(): void beforeUnmount?(): void
unmounted?(): void unmounted?(): void
errorCaptured?(): (err: Error, type: ErrorTypes) => boolean | void errorCaptured?(): (err: Error, type: ErrorTypes) => boolean | void
activated?(): void
deactivated?(): void
_updateHandle: Autorun _updateHandle: Autorun
_queueJob: ((fn: () => void) => void) _queueJob: ((fn: () => void) => void)
@ -95,6 +97,7 @@ class InternalComponent {
public _queueJob: ((fn: () => void) => void) | null = null public _queueJob: ((fn: () => void) => void) | null = null
public _revokeProxy: () => void public _revokeProxy: () => void
public _isVue: boolean = true public _isVue: boolean = true
public _inactiveRoot: boolean = false
constructor(options?: ComponentOptions) { constructor(options?: ComponentOptions) {
this.$options = options || (this.constructor as any).options || EMPTY_OBJ this.$options = options || (this.constructor as any).options || EMPTY_OBJ

View File

@ -122,17 +122,13 @@ export function normalizeComponentRoot(
componentVNode && componentVNode &&
(flags & VNodeFlags.COMPONENT || flags & VNodeFlags.ELEMENT) (flags & VNodeFlags.COMPONENT || flags & VNodeFlags.ELEMENT)
) { ) {
const isKeepAlive = (flags & VNodeFlags.COMPONENT_STATEFUL_KEPT_ALIVE) > 0
if ( if (
inheritAttrs !== false && inheritAttrs !== false &&
attrs !== void 0 && attrs !== void 0 &&
Object.keys(attrs).length > 0 Object.keys(attrs).length > 0
) { ) {
vnode = cloneVNode(vnode, attrs) vnode = cloneVNode(vnode, attrs)
if (isKeepAlive) { } else if (el) {
vnode.el = el
}
} else if (el && !isKeepAlive) {
vnode = cloneVNode(vnode) vnode = cloneVNode(vnode)
} }
if (flags & VNodeFlags.COMPONENT) { if (flags & VNodeFlags.COMPONENT) {

View File

@ -274,8 +274,7 @@ export function createRenderer(options: RendererOptions) {
if (flags & VNodeFlags.COMPONENT_STATEFUL) { if (flags & VNodeFlags.COMPONENT_STATEFUL) {
if (flags & VNodeFlags.COMPONENT_STATEFUL_KEPT_ALIVE) { if (flags & VNodeFlags.COMPONENT_STATEFUL_KEPT_ALIVE) {
// kept-alive // kept-alive
el = vnode.el as RenderNode el = activateComponentInstance(vnode)
// TODO activated hook
} else { } else {
el = mountComponentInstance( el = mountComponentInstance(
vnode, vnode,
@ -287,7 +286,6 @@ export function createRenderer(options: RendererOptions) {
) )
} }
} else { } else {
debugger
// functional component // functional component
const render = tag as FunctionalComponent const render = tag as FunctionalComponent
const { props, attrs } = resolveProps(data, render.props, render) const { props, attrs } = resolveProps(data, render.props, render)
@ -1106,7 +1104,9 @@ export function createRenderer(options: RendererOptions) {
} }
} else if (flags & VNodeFlags.COMPONENT) { } else if (flags & VNodeFlags.COMPONENT) {
if (flags & VNodeFlags.COMPONENT_STATEFUL) { 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) unmountComponentInstance(children as MountedComponent)
} }
} else { } 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 ------------------------------------------------------------ // TODO hydrating ------------------------------------------------------------
// API ----------------------------------------------------------------------- // API -----------------------------------------------------------------------

View File

@ -80,12 +80,14 @@ export const h = ((tag: ElementType, data?: any, children?: any): VNode => {
) )
} else if (tag === Fragment) { } else if (tag === Fragment) {
if (__DEV__ && ref) { 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) return createFragment(children, ChildrenFlags.UNKNOWN_CHILDREN, key)
} else if (tag === Portal) { } else if (tag === Portal) {
if (__DEV__ && !portalTarget) { if (__DEV__ && !portalTarget) {
// TODO warn portal must have a target console.warn('Portal must have a target: ', portalTarget)
} }
return createPortal( return createPortal(
portalTarget, portalTarget,
@ -95,6 +97,12 @@ export const h = ((tag: ElementType, data?: any, children?: any): VNode => {
ref ref
) )
} else { } else {
if (
__DEV__ &&
(!tag || (typeof tag !== 'function' && typeof tag !== 'object'))
) {
console.warn('Invalid component passed to h(): ', tag)
}
// component // component
return createComponentVNode( return createComponentVNode(
tag, tag,

View File

@ -29,7 +29,7 @@ export class KeepAlive extends Component<{}, KeepAliveProps> {
this.keys = new Set() this.keys = new Set()
} }
unmounted() { beforeUnmount() {
this.cache.forEach(vnode => { this.cache.forEach(vnode => {
// change flag so it can be properly unmounted // change flag so it can be properly unmounted
vnode.flags = VNodeFlags.COMPONENT_STATEFUL_NORMAL vnode.flags = VNodeFlags.COMPONENT_STATEFUL_NORMAL
@ -88,21 +88,23 @@ export class KeepAlive extends Component<{}, KeepAliveProps> {
const { cache, keys } = this const { cache, keys } = this
const key = vnode.key == null ? comp : vnode.key const key = vnode.key == null ? comp : vnode.key
const cached = cache.get(key) const cached = cache.get(key)
cache.set(key, vnode)
if (cached) { if (cached) {
vnode.children = cached.children vnode.children = cached.children
vnode.el = cached.el // avoid vnode being mounted as fresh
vnode.flags |= VNodeFlags.COMPONENT_STATEFUL_KEPT_ALIVE vnode.flags |= VNodeFlags.COMPONENT_STATEFUL_KEPT_ALIVE
// make this key the freshest // make this key the freshest
keys.delete(key) keys.delete(key)
keys.add(key) keys.add(key)
} else { } else {
cache.set(key, vnode)
keys.add(key) keys.add(key)
// prune oldest entry // prune oldest entry
if (max && keys.size > parseInt(max, 10)) { if (max && keys.size > parseInt(max, 10)) {
this.pruneCacheEntry(Array.from(this.keys)[0]) this.pruneCacheEntry(Array.from(this.keys)[0])
} }
} }
// avoid vnode being unmounted
vnode.flags |= VNodeFlags.COMPONENT_STATEFUL_SHOULD_KEEP_ALIVE vnode.flags |= VNodeFlags.COMPONENT_STATEFUL_SHOULD_KEEP_ALIVE
return vnode return vnode
} }