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
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

View 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) {

View 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 -----------------------------------------------------------------------

View 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,

View 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
}