wip: refactor

This commit is contained in:
daiwei 2025-04-09 15:49:00 +08:00
parent 61d6f4801b
commit dccc47c265
6 changed files with 67 additions and 59 deletions

View File

@ -73,7 +73,7 @@ export interface KeepAliveContext extends ComponentRenderContext {
deactivate: (vnode: VNode) => void deactivate: (vnode: VNode) => void
} }
export const isKeepAlive = (vnode: VNode): boolean => export const isKeepAlive = (vnode: any): boolean =>
(vnode.type as any).__isKeepAlive (vnode.type as any).__isKeepAlive
const KeepAliveImpl: ComponentOptions = { const KeepAliveImpl: ComponentOptions = {
@ -478,7 +478,7 @@ function injectToKeepAliveRoot(
}, target) }, target)
} }
function resetShapeFlag(vnode: VNode) { export function resetShapeFlag(vnode: any): void {
// bitwise operations to remove keep alive flags // bitwise operations to remove keep alive flags
vnode.shapeFlag &= ~ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE vnode.shapeFlag &= ~ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
vnode.shapeFlag &= ~ShapeFlags.COMPONENT_KEPT_ALIVE vnode.shapeFlag &= ~ShapeFlags.COMPONENT_KEPT_ALIVE

View File

@ -564,7 +564,7 @@ export { getComponentName } from './component'
/** /**
* @internal * @internal
*/ */
export { matches, isKeepAlive } from './components/KeepAlive' export { matches, isKeepAlive, resetShapeFlag } from './components/KeepAlive'
/** /**
* @internal * @internal
*/ */

View File

@ -9,8 +9,8 @@ import {
} from 'vue' } from 'vue'
import type { VaporComponent } from '../../src/component' import type { VaporComponent } from '../../src/component'
import { makeRender } from '../_utils' import { makeRender } from '../_utils'
import { VaporKeepAliveImpl as VaporKeepAlive } from '../../src/components/KeepAlive'
import { import {
VaporKeepAlive,
child, child,
createComponent, createComponent,
createDynamicComponent, createDynamicComponent,
@ -144,7 +144,7 @@ describe('VaporKeepAlive', () => {
const { mount } = define({ const { mount } = define({
setup() { setup() {
const setTemplateRef = createTemplateRefSetter() const setTemplateRef = createTemplateRefSetter()
const n4 = createComponent(VaporKeepAlive as any, null, { const n4 = createComponent(VaporKeepAlive, null, {
default: () => { default: () => {
const n0 = createDynamicComponent(() => views[viewRef.value]) as any const n0 = createDynamicComponent(() => views[viewRef.value]) as any
setTemplateRef(n0, instanceRef) setTemplateRef(n0, instanceRef)
@ -180,7 +180,7 @@ describe('VaporKeepAlive', () => {
return createIf( return createIf(
() => toggle.value, () => toggle.value,
() => () =>
createComponent(VaporKeepAlive as any, null, { createComponent(VaporKeepAlive, null, {
default: () => createDynamicComponent(() => views[viewRef.value]), default: () => createDynamicComponent(() => views[viewRef.value]),
}), }),
) )
@ -235,7 +235,7 @@ describe('VaporKeepAlive', () => {
return createIf( return createIf(
() => toggle.value, () => toggle.value,
() => () =>
createComponent(VaporKeepAlive as any, null, { createComponent(VaporKeepAlive, null, {
default: () => createDynamicComponent(() => views[viewRef.value]), default: () => createDynamicComponent(() => views[viewRef.value]),
}), }),
) )
@ -295,7 +295,7 @@ describe('VaporKeepAlive', () => {
const toggle = ref(true) const toggle = ref(true)
const { html } = define({ const { html } = define({
setup() { setup() {
return createComponent(VaporKeepAlive as any, null, { return createComponent(VaporKeepAlive, null, {
default() { default() {
return createIf( return createIf(
() => toggle.value, () => toggle.value,
@ -350,7 +350,7 @@ describe('VaporKeepAlive', () => {
const toggle = ref(true) const toggle = ref(true)
const { html } = define({ const { html } = define({
setup() { setup() {
return createComponent(VaporKeepAlive as any, null, { return createComponent(VaporKeepAlive, null, {
default() { default() {
return createIf( return createIf(
() => toggle.value, () => toggle.value,
@ -375,7 +375,7 @@ describe('VaporKeepAlive', () => {
onActivated(() => oneHooks.activated()) onActivated(() => oneHooks.activated())
onDeactivated(() => oneHooks.deactivated()) onDeactivated(() => oneHooks.deactivated())
onUnmounted(() => oneHooks.unmounted()) onUnmounted(() => oneHooks.unmounted())
return createComponent(VaporKeepAlive as any, null, { return createComponent(VaporKeepAlive, null, {
default() { default() {
return createIf( return createIf(
() => toggle2.value, () => toggle2.value,
@ -389,7 +389,7 @@ describe('VaporKeepAlive', () => {
const toggle1 = ref(true) const toggle1 = ref(true)
const { html } = define({ const { html } = define({
setup() { setup() {
return createComponent(VaporKeepAlive as any, null, { return createComponent(VaporKeepAlive, null, {
default() { default() {
return createIf( return createIf(
() => toggle1.value, () => toggle1.value,

View File

@ -1,6 +1,7 @@
import { isArray } from '@vue/shared' import { isArray } from '@vue/shared'
import { import {
type VaporComponentInstance, type VaporComponentInstance,
currentInstance,
isVaporComponent, isVaporComponent,
mountComponent, mountComponent,
unmountComponent, unmountComponent,
@ -8,6 +9,8 @@ import {
import { createComment, createTextNode } from './dom/node' import { createComment, createTextNode } from './dom/node'
import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity' import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity'
import { isHydrating } from './dom/hydration' import { isHydrating } from './dom/hydration'
import { isKeepAlive } from 'vue'
import type { KeepAliveInstance } from './components/KeepAlive'
export type Block = export type Block =
| Node | Node
@ -50,8 +53,14 @@ export class DynamicFragment extends VaporFragment {
pauseTracking() pauseTracking()
const parent = this.anchor.parentNode const parent = this.anchor.parentNode
const instance = currentInstance!
// teardown previous branch // teardown previous branch
if (this.scope) { if (this.scope) {
if (isKeepAlive(instance)) {
;(instance as KeepAliveInstance).process(
this.nodes as VaporComponentInstance,
)
}
this.scope.stop() this.scope.stop()
parent && remove(this.nodes, parent) parent && remove(this.nodes, parent)
} }
@ -59,6 +68,11 @@ export class DynamicFragment extends VaporFragment {
if (render) { if (render) {
this.scope = new EffectScope() this.scope = new EffectScope()
this.nodes = this.scope.run(render) || [] this.nodes = this.scope.run(render) || []
if (isKeepAlive(instance)) {
;(instance as KeepAliveInstance).process(
this.nodes as VaporComponentInstance,
)
}
if (parent) insert(this.nodes, parent, this.anchor) if (parent) insert(this.nodes, parent, this.anchor)
} else { } else {
this.scope = undefined this.scope = undefined

View File

@ -15,7 +15,6 @@ import {
currentInstance, currentInstance,
endMeasure, endMeasure,
expose, expose,
isKeepAlive,
nextUid, nextUid,
popWarningContext, popWarningContext,
pushWarningContext, pushWarningContext,
@ -36,7 +35,13 @@ import {
resetTracking, resetTracking,
unref, unref,
} from '@vue/reactivity' } from '@vue/reactivity'
import { EMPTY_OBJ, invokeArrayFns, isFunction, isString } from '@vue/shared' import {
EMPTY_OBJ,
ShapeFlags,
invokeArrayFns,
isFunction,
isString,
} from '@vue/shared'
import { import {
type DynamicPropsSource, type DynamicPropsSource,
type RawProps, type RawProps,
@ -376,6 +381,7 @@ export class VaporComponentInstance implements GenericComponentInstance {
propsOptions?: NormalizedPropsOptions propsOptions?: NormalizedPropsOptions
emitsOptions?: ObjectEmitsOptions | null emitsOptions?: ObjectEmitsOptions | null
isSingleRoot?: boolean isSingleRoot?: boolean
shapeFlag?: number
constructor( constructor(
comp: VaporComponent, comp: VaporComponent,
@ -498,13 +504,12 @@ export function mountComponent(
parentNode: ParentNode, parentNode: ParentNode,
anchor?: Node | null | 0, anchor?: Node | null | 0,
): void { ): void {
let parent if (instance.shapeFlag! & ShapeFlags.COMPONENT_KEPT_ALIVE) {
if ( ;(instance.parent as KeepAliveInstance).activate(
(parent = instance.parent) && instance,
isKeepAlive(parent as any) && parentNode,
(parent as KeepAliveInstance).isKeptAlive(instance) anchor as any,
) { )
;(parent as KeepAliveInstance).activate(instance, parentNode, anchor as any)
instance.isMounted = true instance.isMounted = true
return return
} }
@ -516,9 +521,7 @@ export function mountComponent(
insert(instance.block, parentNode, anchor) insert(instance.block, parentNode, anchor)
if (instance.m) queuePostFlushCb(() => invokeArrayFns(instance.m!)) if (instance.m) queuePostFlushCb(() => invokeArrayFns(instance.m!))
if ( if (
parent && instance.shapeFlag! & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE &&
isKeepAlive(parent as any) &&
(parent as KeepAliveInstance).shouldKeepAlive(instance) &&
instance.a instance.a
) { ) {
queuePostFlushCb(instance.a!) queuePostFlushCb(instance.a!)
@ -533,13 +536,8 @@ export function unmountComponent(
instance: VaporComponentInstance, instance: VaporComponentInstance,
parentNode?: ParentNode, parentNode?: ParentNode,
): void { ): void {
let parent if (instance.shapeFlag! & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
if ( ;(instance.parent as KeepAliveInstance).deactivate(instance)
(parent = instance.parent) &&
isKeepAlive(parent as any) &&
(parent as KeepAliveInstance).shouldKeepAlive(instance)
) {
;(parent as KeepAliveInstance).deactivate(instance)
return return
} }

View File

@ -3,25 +3,25 @@ import {
currentInstance, currentInstance,
devtoolsComponentAdded, devtoolsComponentAdded,
getComponentName, getComponentName,
invalidateMount,
isKeepAlive,
matches, matches,
onBeforeUnmount, onBeforeUnmount,
onMounted, onMounted,
onUpdated, onUpdated,
queuePostFlushCb, queuePostFlushCb,
resetShapeFlag,
warn, warn,
watch, watch,
} from '@vue/runtime-dom' } from '@vue/runtime-dom'
import { type Block, insert, isFragment, isValidBlock } from '../block' import { type Block, insert, isFragment, isValidBlock } from '../block'
import { import {
type ObjectVaporComponent,
type VaporComponent, type VaporComponent,
type VaporComponentInstance, type VaporComponentInstance,
isVaporComponent, isVaporComponent,
unmountComponent, unmountComponent,
} from '../component' } from '../component'
import { defineVaporComponent } from '../apiDefineComponent' import { defineVaporComponent } from '../apiDefineComponent'
import { invokeArrayFns, isArray } from '@vue/shared' import { ShapeFlags, invokeArrayFns, isArray } from '@vue/shared'
export interface KeepAliveInstance extends VaporComponentInstance { export interface KeepAliveInstance extends VaporComponentInstance {
activate: ( activate: (
@ -30,15 +30,14 @@ export interface KeepAliveInstance extends VaporComponentInstance {
anchor: Node, anchor: Node,
) => void ) => void
deactivate: (instance: VaporComponentInstance) => void deactivate: (instance: VaporComponentInstance) => void
shouldKeepAlive: (instance: VaporComponentInstance) => boolean process: (instance: VaporComponentInstance) => void
isKeptAlive: (instance: VaporComponentInstance) => boolean
} }
type CacheKey = PropertyKey | VaporComponent type CacheKey = PropertyKey | VaporComponent
type Cache = Map<CacheKey, VaporComponentInstance> type Cache = Map<CacheKey, VaporComponentInstance>
type Keys = Set<CacheKey> type Keys = Set<CacheKey>
const VaporKeepAliveImpl = defineVaporComponent({ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({
name: 'VaporKeepAlive', name: 'VaporKeepAlive',
// @ts-expect-error // @ts-expect-error
__isKeepAlive: true, __isKeepAlive: true,
@ -57,7 +56,6 @@ const VaporKeepAliveImpl = defineVaporComponent({
const keys: Keys = new Set() const keys: Keys = new Set()
const storageContainer = document.createElement('div') const storageContainer = document.createElement('div')
let current: VaporComponentInstance | undefined let current: VaporComponentInstance | undefined
let isUnmounting = false
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
;(keepAliveInstance as any).__v_cache = cache ;(keepAliveInstance as any).__v_cache = cache
@ -90,9 +88,10 @@ const VaporKeepAliveImpl = defineVaporComponent({
onMounted(cacheBlock) onMounted(cacheBlock)
onUpdated(cacheBlock) onUpdated(cacheBlock)
onBeforeUnmount(() => { onBeforeUnmount(() => {
isUnmounting = true
cache.forEach(cached => { cache.forEach(cached => {
resetShapeFlag(cached)
cache.delete(cached.type) cache.delete(cached.type)
// current instance will be unmounted as part of keep-alive's unmount // current instance will be unmounted as part of keep-alive's unmount
if (current && current.type === cached.type) { if (current && current.type === cached.type) {
@ -104,12 +103,20 @@ const VaporKeepAliveImpl = defineVaporComponent({
}) })
}) })
const children = slots.default() keepAliveInstance.process = (instance: VaporComponentInstance) => {
if (isArray(children) && children.length > 1) { if (cache.has(instance.type)) {
if (__DEV__) { instance.shapeFlag! |= ShapeFlags.COMPONENT_KEPT_ALIVE
warn(`KeepAlive should contain exactly one component child.`) }
const name = getComponentName(instance.type)
if (
!(
(include && (!name || !matches(include, name))) ||
(exclude && name && matches(exclude, name))
)
) {
instance.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
} }
return children
} }
keepAliveInstance.activate = ( keepAliveInstance.activate = (
@ -117,9 +124,6 @@ const VaporKeepAliveImpl = defineVaporComponent({
parentNode: ParentNode, parentNode: ParentNode,
anchor: Node, anchor: Node,
) => { ) => {
// invalidateMount(instance.m)
// invalidateMount(instance.a)
const cachedBlock = cache.get(instance.type)! const cachedBlock = cache.get(instance.type)!
insert((instance.block = cachedBlock.block), parentNode, anchor) insert((instance.block = cachedBlock.block), parentNode, anchor)
queuePostFlushCb(() => { queuePostFlushCb(() => {
@ -144,20 +148,12 @@ const VaporKeepAliveImpl = defineVaporComponent({
} }
} }
keepAliveInstance.shouldKeepAlive = (instance: VaporComponentInstance) => { const children = slots.default()
if (isUnmounting) return false if (isArray(children) && children.length > 1) {
const name = getComponentName(instance.type) if (__DEV__) {
if ( warn(`KeepAlive should contain exactly one component child.`)
(include && (!name || !matches(include, name))) ||
(exclude && name && matches(exclude, name))
) {
return false
} }
return true return children
}
keepAliveInstance.isKeptAlive = (instance: VaporComponentInstance) => {
return cache.has(instance.type)
} }
function pruneCache(filter: (name: string) => boolean) { function pruneCache(filter: (name: string) => boolean) {