feat: activated/deactivated hooks
This commit is contained in:
parent
7c2ec8ace0
commit
ee50fb9723
@ -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
|
||||||
|
@ -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) {
|
||||||
|
@ -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 -----------------------------------------------------------------------
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user