test: tests for keep-alive
This commit is contained in:
parent
a42d165285
commit
5fcb81050a
233
packages/runtime-core/__tests__/keepAlive.spec.ts
Normal file
233
packages/runtime-core/__tests__/keepAlive.spec.ts
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
import { ComponentOptions } from '../src/component'
|
||||||
|
import {
|
||||||
|
h,
|
||||||
|
TestElement,
|
||||||
|
nodeOps,
|
||||||
|
render,
|
||||||
|
ref,
|
||||||
|
KeepAlive,
|
||||||
|
serializeInner,
|
||||||
|
nextTick
|
||||||
|
} from '@vue/runtime-test'
|
||||||
|
|
||||||
|
describe('keep-alive', () => {
|
||||||
|
let one: ComponentOptions
|
||||||
|
let two: ComponentOptions
|
||||||
|
let root: TestElement
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
root = nodeOps.createElement('div')
|
||||||
|
one = {
|
||||||
|
data: () => ({ msg: 'one' }),
|
||||||
|
render() {
|
||||||
|
return h('div', this.msg)
|
||||||
|
},
|
||||||
|
created: jest.fn(),
|
||||||
|
mounted: jest.fn(),
|
||||||
|
activated: jest.fn(),
|
||||||
|
deactivated: jest.fn(),
|
||||||
|
unmounted: jest.fn()
|
||||||
|
}
|
||||||
|
two = {
|
||||||
|
data: () => ({ msg: 'two' }),
|
||||||
|
render() {
|
||||||
|
return h('div', this.msg)
|
||||||
|
},
|
||||||
|
created: jest.fn(),
|
||||||
|
mounted: jest.fn(),
|
||||||
|
activated: jest.fn(),
|
||||||
|
deactivated: jest.fn(),
|
||||||
|
unmounted: jest.fn()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function assertHookCalls(component: any, callCounts: number[]) {
|
||||||
|
expect([
|
||||||
|
component.created.mock.calls.length,
|
||||||
|
component.mounted.mock.calls.length,
|
||||||
|
component.activated.mock.calls.length,
|
||||||
|
component.deactivated.mock.calls.length,
|
||||||
|
component.unmounted.mock.calls.length
|
||||||
|
]).toEqual(callCounts)
|
||||||
|
}
|
||||||
|
|
||||||
|
test('should preserve state', async () => {
|
||||||
|
const toggle = ref(true)
|
||||||
|
const instanceRef = ref<any>(null)
|
||||||
|
const App = {
|
||||||
|
render() {
|
||||||
|
return h(KeepAlive, null, {
|
||||||
|
default: () => h(toggle.value ? one : two, { ref: instanceRef })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render(h(App), root)
|
||||||
|
expect(serializeInner(root)).toBe(`<div>one</div>`)
|
||||||
|
instanceRef.value.msg = 'changed'
|
||||||
|
await nextTick()
|
||||||
|
expect(serializeInner(root)).toBe(`<div>changed</div>`)
|
||||||
|
toggle.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect(serializeInner(root)).toBe(`<div>two</div>`)
|
||||||
|
toggle.value = true
|
||||||
|
await nextTick()
|
||||||
|
expect(serializeInner(root)).toBe(`<div>changed</div>`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should call correct lifecycle hooks', async () => {
|
||||||
|
const toggle1 = ref(true)
|
||||||
|
const toggle2 = ref(true)
|
||||||
|
const App = {
|
||||||
|
render() {
|
||||||
|
return toggle1.value
|
||||||
|
? h(KeepAlive, () => h(toggle2.value ? one : two))
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render(h(App), root)
|
||||||
|
|
||||||
|
expect(serializeInner(root)).toBe(`<div>one</div>`)
|
||||||
|
assertHookCalls(one, [1, 1, 1, 0, 0])
|
||||||
|
assertHookCalls(two, [0, 0, 0, 0, 0])
|
||||||
|
|
||||||
|
// toggle kept-alive component
|
||||||
|
toggle2.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect(serializeInner(root)).toBe(`<div>two</div>`)
|
||||||
|
assertHookCalls(one, [1, 1, 1, 1, 0])
|
||||||
|
assertHookCalls(two, [1, 1, 1, 0, 0])
|
||||||
|
|
||||||
|
toggle2.value = true
|
||||||
|
await nextTick()
|
||||||
|
expect(serializeInner(root)).toBe(`<div>one</div>`)
|
||||||
|
assertHookCalls(one, [1, 1, 2, 1, 0])
|
||||||
|
assertHookCalls(two, [1, 1, 1, 1, 0])
|
||||||
|
|
||||||
|
toggle2.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect(serializeInner(root)).toBe(`<div>two</div>`)
|
||||||
|
assertHookCalls(one, [1, 1, 2, 2, 0])
|
||||||
|
assertHookCalls(two, [1, 1, 2, 1, 0])
|
||||||
|
|
||||||
|
// teardown keep-alive, should unmount all components including cached
|
||||||
|
toggle1.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect(serializeInner(root)).toBe(`<!---->`)
|
||||||
|
assertHookCalls(one, [1, 1, 2, 2, 1])
|
||||||
|
assertHookCalls(two, [1, 1, 2, 2, 1])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should call lifecycle hooks on nested components', async () => {
|
||||||
|
one.render = () => h(two)
|
||||||
|
|
||||||
|
const toggle = ref(true)
|
||||||
|
const App = {
|
||||||
|
render() {
|
||||||
|
return h(KeepAlive, () => (toggle.value ? h(one) : null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render(h(App), root)
|
||||||
|
|
||||||
|
expect(serializeInner(root)).toBe(`<div>two</div>`)
|
||||||
|
assertHookCalls(one, [1, 1, 1, 0, 0])
|
||||||
|
assertHookCalls(two, [1, 1, 1, 0, 0])
|
||||||
|
|
||||||
|
toggle.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect(serializeInner(root)).toBe(`<!---->`)
|
||||||
|
assertHookCalls(one, [1, 1, 1, 1, 0])
|
||||||
|
assertHookCalls(two, [1, 1, 1, 1, 0])
|
||||||
|
|
||||||
|
toggle.value = true
|
||||||
|
await nextTick()
|
||||||
|
expect(serializeInner(root)).toBe(`<div>two</div>`)
|
||||||
|
assertHookCalls(one, [1, 1, 2, 1, 0])
|
||||||
|
assertHookCalls(two, [1, 1, 2, 1, 0])
|
||||||
|
|
||||||
|
toggle.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect(serializeInner(root)).toBe(`<!---->`)
|
||||||
|
assertHookCalls(one, [1, 1, 2, 2, 0])
|
||||||
|
assertHookCalls(two, [1, 1, 2, 2, 0])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should call correct hooks for nested keep-alive', async () => {
|
||||||
|
const toggle2 = ref(true)
|
||||||
|
one.render = () => h(KeepAlive, () => (toggle2.value ? h(two) : null))
|
||||||
|
|
||||||
|
const toggle1 = ref(true)
|
||||||
|
const App = {
|
||||||
|
render() {
|
||||||
|
return h(KeepAlive, () => (toggle1.value ? h(one) : null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render(h(App), root)
|
||||||
|
|
||||||
|
expect(serializeInner(root)).toBe(`<div>two</div>`)
|
||||||
|
assertHookCalls(one, [1, 1, 1, 0, 0])
|
||||||
|
assertHookCalls(two, [1, 1, 1, 0, 0])
|
||||||
|
|
||||||
|
toggle1.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect(serializeInner(root)).toBe(`<!---->`)
|
||||||
|
assertHookCalls(one, [1, 1, 1, 1, 0])
|
||||||
|
assertHookCalls(two, [1, 1, 1, 1, 0])
|
||||||
|
|
||||||
|
toggle1.value = true
|
||||||
|
await nextTick()
|
||||||
|
expect(serializeInner(root)).toBe(`<div>two</div>`)
|
||||||
|
assertHookCalls(one, [1, 1, 2, 1, 0])
|
||||||
|
assertHookCalls(two, [1, 1, 2, 1, 0])
|
||||||
|
|
||||||
|
// toggle nested instance
|
||||||
|
toggle2.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect(serializeInner(root)).toBe(`<!---->`)
|
||||||
|
assertHookCalls(one, [1, 1, 2, 1, 0])
|
||||||
|
assertHookCalls(two, [1, 1, 2, 2, 0])
|
||||||
|
|
||||||
|
toggle2.value = true
|
||||||
|
await nextTick()
|
||||||
|
expect(serializeInner(root)).toBe(`<div>two</div>`)
|
||||||
|
assertHookCalls(one, [1, 1, 2, 1, 0])
|
||||||
|
assertHookCalls(two, [1, 1, 3, 2, 0])
|
||||||
|
|
||||||
|
toggle1.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect(serializeInner(root)).toBe(`<!---->`)
|
||||||
|
assertHookCalls(one, [1, 1, 2, 2, 0])
|
||||||
|
assertHookCalls(two, [1, 1, 3, 3, 0])
|
||||||
|
|
||||||
|
// toggle nested instance when parent is deactivated
|
||||||
|
toggle2.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect(serializeInner(root)).toBe(`<!---->`)
|
||||||
|
assertHookCalls(one, [1, 1, 2, 2, 0])
|
||||||
|
assertHookCalls(two, [1, 1, 3, 3, 0]) // should not be affected
|
||||||
|
|
||||||
|
toggle2.value = true
|
||||||
|
await nextTick()
|
||||||
|
expect(serializeInner(root)).toBe(`<!---->`)
|
||||||
|
assertHookCalls(one, [1, 1, 2, 2, 0])
|
||||||
|
assertHookCalls(two, [1, 1, 3, 3, 0]) // should not be affected
|
||||||
|
|
||||||
|
toggle1.value = true
|
||||||
|
await nextTick()
|
||||||
|
expect(serializeInner(root)).toBe(`<div>two</div>`)
|
||||||
|
assertHookCalls(one, [1, 1, 3, 2, 0])
|
||||||
|
assertHookCalls(two, [1, 1, 4, 3, 0])
|
||||||
|
|
||||||
|
toggle1.value = false
|
||||||
|
toggle2.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect(serializeInner(root)).toBe(`<!---->`)
|
||||||
|
assertHookCalls(one, [1, 1, 3, 3, 0])
|
||||||
|
assertHookCalls(two, [1, 1, 4, 4, 0])
|
||||||
|
|
||||||
|
toggle1.value = true
|
||||||
|
await nextTick()
|
||||||
|
expect(serializeInner(root)).toBe(`<!---->`)
|
||||||
|
assertHookCalls(one, [1, 1, 4, 3, 0])
|
||||||
|
assertHookCalls(two, [1, 1, 4, 4, 0]) // should remain inactive
|
||||||
|
})
|
||||||
|
})
|
@ -9,17 +9,23 @@ import { callWithAsyncErrorHandling, ErrorTypeStrings } from './errorHandling'
|
|||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { capitalize } from '@vue/shared'
|
import { capitalize } from '@vue/shared'
|
||||||
import { pauseTracking, resumeTracking, DebuggerEvent } from '@vue/reactivity'
|
import { pauseTracking, resumeTracking, DebuggerEvent } from '@vue/reactivity'
|
||||||
import { registerKeepAliveHook } from './keepAlive'
|
|
||||||
|
export { onActivated, onDeactivated } from './keepAlive'
|
||||||
|
|
||||||
export function injectHook(
|
export function injectHook(
|
||||||
type: LifecycleHooks,
|
type: LifecycleHooks,
|
||||||
hook: Function,
|
hook: Function & { __weh?: Function },
|
||||||
target: ComponentInternalInstance | null = currentInstance,
|
target: ComponentInternalInstance | null = currentInstance,
|
||||||
prepend: boolean = false
|
prepend: boolean = false
|
||||||
) {
|
) {
|
||||||
if (target) {
|
if (target) {
|
||||||
const hooks = target[type] || (target[type] = [])
|
const hooks = target[type] || (target[type] = [])
|
||||||
const wrappedHook = (...args: unknown[]) => {
|
// cache the error handling wrapper for injected hooks so the same hook
|
||||||
|
// can be properly deduped by the scheduler. "__weh" stands for "with error
|
||||||
|
// handling".
|
||||||
|
const wrappedHook =
|
||||||
|
hook.__weh ||
|
||||||
|
(hook.__weh = (...args: unknown[]) => {
|
||||||
if (target.isUnmounted) {
|
if (target.isUnmounted) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -34,7 +40,7 @@ export function injectHook(
|
|||||||
setCurrentInstance(null)
|
setCurrentInstance(null)
|
||||||
resumeTracking()
|
resumeTracking()
|
||||||
return res
|
return res
|
||||||
}
|
})
|
||||||
if (prepend) {
|
if (prepend) {
|
||||||
hooks.unshift(wrappedHook)
|
hooks.unshift(wrappedHook)
|
||||||
} else {
|
} else {
|
||||||
@ -84,17 +90,3 @@ export type ErrorCapturedHook = (
|
|||||||
export const onErrorCaptured = createHook<ErrorCapturedHook>(
|
export const onErrorCaptured = createHook<ErrorCapturedHook>(
|
||||||
LifecycleHooks.ERROR_CAPTURED
|
LifecycleHooks.ERROR_CAPTURED
|
||||||
)
|
)
|
||||||
|
|
||||||
export function onActivated(
|
|
||||||
hook: Function,
|
|
||||||
target?: ComponentInternalInstance | null
|
|
||||||
) {
|
|
||||||
registerKeepAliveHook(hook, LifecycleHooks.ACTIVATED, target)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function onDeactivated(
|
|
||||||
hook: Function,
|
|
||||||
target?: ComponentInternalInstance | null
|
|
||||||
) {
|
|
||||||
registerKeepAliveHook(hook, LifecycleHooks.DEACTIVATED, target)
|
|
||||||
}
|
|
||||||
|
@ -863,6 +863,7 @@ export function createRenderer<
|
|||||||
|
|
||||||
setupRenderEffect(
|
setupRenderEffect(
|
||||||
instance,
|
instance,
|
||||||
|
parentComponent,
|
||||||
parentSuspense,
|
parentSuspense,
|
||||||
initialVNode,
|
initialVNode,
|
||||||
container,
|
container,
|
||||||
@ -877,6 +878,7 @@ export function createRenderer<
|
|||||||
|
|
||||||
function setupRenderEffect(
|
function setupRenderEffect(
|
||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
|
parentComponent: ComponentInternalInstance | null,
|
||||||
parentSuspense: HostSuspenseBoundary | null,
|
parentSuspense: HostSuspenseBoundary | null,
|
||||||
initialVNode: HostVNode,
|
initialVNode: HostVNode,
|
||||||
container: HostElement,
|
container: HostElement,
|
||||||
@ -898,6 +900,10 @@ export function createRenderer<
|
|||||||
if (instance.m !== null) {
|
if (instance.m !== null) {
|
||||||
queuePostRenderEffect(instance.m, parentSuspense)
|
queuePostRenderEffect(instance.m, parentSuspense)
|
||||||
}
|
}
|
||||||
|
// activated hook for keep-alive roots.
|
||||||
|
if (instance.a !== null) {
|
||||||
|
queuePostRenderEffect(instance.a, parentSuspense)
|
||||||
|
}
|
||||||
mounted = true
|
mounted = true
|
||||||
} else {
|
} else {
|
||||||
// updateComponent
|
// updateComponent
|
||||||
@ -1450,7 +1456,7 @@ export function createRenderer<
|
|||||||
parentSuspense: HostSuspenseBoundary | null,
|
parentSuspense: HostSuspenseBoundary | null,
|
||||||
doRemove?: boolean
|
doRemove?: boolean
|
||||||
) {
|
) {
|
||||||
const { bum, effects, update, subTree, um } = instance
|
const { bum, effects, update, subTree, um, da, isDeactivated } = instance
|
||||||
// beforeUnmount hook
|
// beforeUnmount hook
|
||||||
if (bum !== null) {
|
if (bum !== null) {
|
||||||
invokeHooks(bum)
|
invokeHooks(bum)
|
||||||
@ -1470,6 +1476,10 @@ export function createRenderer<
|
|||||||
if (um !== null) {
|
if (um !== null) {
|
||||||
queuePostRenderEffect(um, parentSuspense)
|
queuePostRenderEffect(um, parentSuspense)
|
||||||
}
|
}
|
||||||
|
// deactivated hook
|
||||||
|
if (da !== null && !isDeactivated) {
|
||||||
|
queuePostRenderEffect(da, parentSuspense)
|
||||||
|
}
|
||||||
queuePostFlushCb(() => {
|
queuePostFlushCb(() => {
|
||||||
instance.isUnmounted = true
|
instance.isUnmounted = true
|
||||||
})
|
})
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
} from './component'
|
} from './component'
|
||||||
import { VNode, cloneVNode, isVNode } from './vnode'
|
import { VNode, cloneVNode, isVNode } from './vnode'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { onBeforeUnmount, injectHook } from './apiLifecycle'
|
import { onBeforeUnmount, injectHook, onUnmounted } from './apiLifecycle'
|
||||||
import { isString, isArray } from '@vue/shared'
|
import { isString, isArray } from '@vue/shared'
|
||||||
import { watch } from './apiWatch'
|
import { watch } from './apiWatch'
|
||||||
import { ShapeFlags } from './shapeFlags'
|
import { ShapeFlags } from './shapeFlags'
|
||||||
@ -203,34 +203,31 @@ function matches(pattern: MatchPattern, name: string): boolean {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerKeepAliveHook(
|
export function onActivated(
|
||||||
hook: Function,
|
hook: Function,
|
||||||
|
target?: ComponentInternalInstance | null
|
||||||
|
) {
|
||||||
|
registerKeepAliveHook(hook, LifecycleHooks.ACTIVATED, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function onDeactivated(
|
||||||
|
hook: Function,
|
||||||
|
target?: ComponentInternalInstance | null
|
||||||
|
) {
|
||||||
|
registerKeepAliveHook(hook, LifecycleHooks.DEACTIVATED, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerKeepAliveHook(
|
||||||
|
hook: Function & { __wdc?: Function },
|
||||||
type: LifecycleHooks,
|
type: LifecycleHooks,
|
||||||
target: ComponentInternalInstance | null = currentInstance
|
target: ComponentInternalInstance | null = currentInstance
|
||||||
) {
|
) {
|
||||||
// When registering an activated/deactivated hook, instead of registering it
|
// cache the deactivate branch check wrapper for injected hooks so the same
|
||||||
// on the target instance, we walk up the parent chain and register it on
|
// hook can be properly deduped by the scheduler. "__wdc" stands for "with
|
||||||
// every ancestor instance that is a keep-alive root. This avoids the need
|
// deactivation check".
|
||||||
// to walk the entire component tree when invoking these hooks, and more
|
const wrappedHook =
|
||||||
// importantly, avoids the need to track child components in arrays.
|
hook.__wdc ||
|
||||||
if (target) {
|
(hook.__wdc = () => {
|
||||||
let current = target
|
|
||||||
while (current.parent) {
|
|
||||||
if (current.parent.type === KeepAlive) {
|
|
||||||
register(hook, type, target, current)
|
|
||||||
}
|
|
||||||
current = current.parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function register(
|
|
||||||
hook: Function,
|
|
||||||
type: LifecycleHooks,
|
|
||||||
target: ComponentInternalInstance,
|
|
||||||
keepAliveRoot: ComponentInternalInstance
|
|
||||||
) {
|
|
||||||
const wrappedHook = () => {
|
|
||||||
// only fire the hook if the target instance is NOT in a deactivated branch.
|
// only fire the hook if the target instance is NOT in a deactivated branch.
|
||||||
let current: ComponentInternalInstance | null = target
|
let current: ComponentInternalInstance | null = target
|
||||||
while (current) {
|
while (current) {
|
||||||
@ -240,10 +237,33 @@ function register(
|
|||||||
current = current.parent
|
current = current.parent
|
||||||
}
|
}
|
||||||
hook()
|
hook()
|
||||||
|
})
|
||||||
|
injectHook(type, wrappedHook, target)
|
||||||
|
// In addition to registering it on the target instance, we walk up the parent
|
||||||
|
// chain and register it on all ancestor instances that are keep-alive roots.
|
||||||
|
// This avoids the need to walk the entire component tree when invoking these
|
||||||
|
// hooks, and more importantly, avoids the need to track child components in
|
||||||
|
// arrays.
|
||||||
|
if (target) {
|
||||||
|
let current = target.parent
|
||||||
|
while (current && current.parent) {
|
||||||
|
if (current.parent.type === KeepAlive) {
|
||||||
|
injectToKeepAliveRoot(wrappedHook, type, target, current)
|
||||||
}
|
}
|
||||||
injectHook(type, wrappedHook, keepAliveRoot, true)
|
current = current.parent
|
||||||
onBeforeUnmount(() => {
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function injectToKeepAliveRoot(
|
||||||
|
hook: Function,
|
||||||
|
type: LifecycleHooks,
|
||||||
|
target: ComponentInternalInstance,
|
||||||
|
keepAliveRoot: ComponentInternalInstance
|
||||||
|
) {
|
||||||
|
injectHook(type, hook, keepAliveRoot, true /* prepend */)
|
||||||
|
onUnmounted(() => {
|
||||||
const hooks = keepAliveRoot[type]!
|
const hooks = keepAliveRoot[type]!
|
||||||
hooks.splice(hooks.indexOf(wrappedHook), 1)
|
hooks.splice(hooks.indexOf(hook), 1)
|
||||||
}, target)
|
}, target)
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ const postFlushCbs: Function[] = []
|
|||||||
const p = Promise.resolve()
|
const p = Promise.resolve()
|
||||||
|
|
||||||
let isFlushing = false
|
let isFlushing = false
|
||||||
|
let isFlushPending = false
|
||||||
|
|
||||||
export function nextTick(fn?: () => void): Promise<void> {
|
export function nextTick(fn?: () => void): Promise<void> {
|
||||||
return fn ? p.then(fn) : p
|
return fn ? p.then(fn) : p
|
||||||
@ -14,9 +15,7 @@ export function nextTick(fn?: () => void): Promise<void> {
|
|||||||
export function queueJob(job: () => void) {
|
export function queueJob(job: () => void) {
|
||||||
if (!queue.includes(job)) {
|
if (!queue.includes(job)) {
|
||||||
queue.push(job)
|
queue.push(job)
|
||||||
if (!isFlushing) {
|
queueFlush()
|
||||||
nextTick(flushJobs)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,8 +25,12 @@ export function queuePostFlushCb(cb: Function | Function[]) {
|
|||||||
} else {
|
} else {
|
||||||
postFlushCbs.push(...cb)
|
postFlushCbs.push(...cb)
|
||||||
}
|
}
|
||||||
|
queueFlush()
|
||||||
|
}
|
||||||
|
|
||||||
if (!isFlushing) {
|
function queueFlush() {
|
||||||
|
if (!isFlushing && !isFlushPending) {
|
||||||
|
isFlushPending = true
|
||||||
nextTick(flushJobs)
|
nextTick(flushJobs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,6 +51,7 @@ const RECURSION_LIMIT = 100
|
|||||||
type JobCountMap = Map<Function, number>
|
type JobCountMap = Map<Function, number>
|
||||||
|
|
||||||
function flushJobs(seenJobs?: JobCountMap) {
|
function flushJobs(seenJobs?: JobCountMap) {
|
||||||
|
isFlushPending = false
|
||||||
isFlushing = true
|
isFlushing = true
|
||||||
let job
|
let job
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
@ -77,7 +81,7 @@ function flushJobs(seenJobs?: JobCountMap) {
|
|||||||
isFlushing = false
|
isFlushing = false
|
||||||
// some postFlushCb queued jobs!
|
// some postFlushCb queued jobs!
|
||||||
// keep flushing until it drains.
|
// keep flushing until it drains.
|
||||||
if (queue.length) {
|
if (queue.length || postFlushCbs.length) {
|
||||||
flushJobs(seenJobs)
|
flushJobs(seenJobs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,6 +203,7 @@ export interface SuspenseBoundary<
|
|||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
setupRenderEffect: (
|
setupRenderEffect: (
|
||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
|
parentComponent: ComponentInternalInstance | null,
|
||||||
parentSuspense: SuspenseBoundary<HostNode, HostElement> | null,
|
parentSuspense: SuspenseBoundary<HostNode, HostElement> | null,
|
||||||
initialVNode: VNode<HostNode, HostElement>,
|
initialVNode: VNode<HostNode, HostElement>,
|
||||||
container: HostElement,
|
container: HostElement,
|
||||||
@ -402,6 +403,7 @@ function createSuspenseBoundary<HostNode, HostElement>(
|
|||||||
handleSetupResult(instance, asyncSetupResult, suspense)
|
handleSetupResult(instance, asyncSetupResult, suspense)
|
||||||
setupRenderEffect(
|
setupRenderEffect(
|
||||||
instance,
|
instance,
|
||||||
|
parentComponent,
|
||||||
suspense,
|
suspense,
|
||||||
vnode,
|
vnode,
|
||||||
// component may have been moved before resolve
|
// component may have been moved before resolve
|
||||||
|
Loading…
x
Reference in New Issue
Block a user