fix(keep-alive): include/exclude should work with async component (#3531)
fix #3529
This commit is contained in:
parent
f1f52300e6
commit
9e3708ca75
@ -14,10 +14,14 @@ import {
|
||||
ComponentPublicInstance,
|
||||
Ref,
|
||||
cloneVNode,
|
||||
provide
|
||||
provide,
|
||||
defineAsyncComponent,
|
||||
Component
|
||||
} from '@vue/runtime-test'
|
||||
import { KeepAliveProps } from '../../src/components/KeepAlive'
|
||||
|
||||
const timeout = (n: number = 0) => new Promise(r => setTimeout(r, n))
|
||||
|
||||
describe('KeepAlive', () => {
|
||||
let one: ComponentOptions
|
||||
let two: ComponentOptions
|
||||
@ -823,4 +827,53 @@ describe('KeepAlive', () => {
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(`<div foo>changed</div>`)
|
||||
})
|
||||
|
||||
test('should work with async component', async () => {
|
||||
let resolve: (comp: Component) => void
|
||||
const AsyncComp = defineAsyncComponent(
|
||||
() =>
|
||||
new Promise(r => {
|
||||
resolve = r as any
|
||||
})
|
||||
)
|
||||
|
||||
const toggle = ref(true)
|
||||
const instanceRef = ref<any>(null)
|
||||
const App = {
|
||||
render: () => {
|
||||
return h(
|
||||
KeepAlive,
|
||||
{ include: 'Foo' },
|
||||
() => (toggle.value ? h(AsyncComp, { ref: instanceRef }) : null)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
render(h(App), root)
|
||||
// async component has not been resolved
|
||||
expect(serializeInner(root)).toBe('<!---->')
|
||||
|
||||
resolve!({
|
||||
name: 'Foo',
|
||||
data: () => ({ count: 0 }),
|
||||
render() {
|
||||
return h('p', this.count)
|
||||
}
|
||||
})
|
||||
|
||||
await timeout()
|
||||
// resolved
|
||||
expect(serializeInner(root)).toBe('<p>0</p>')
|
||||
|
||||
// change state + toggle out
|
||||
instanceRef.value.count++
|
||||
toggle.value = false
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe('<!---->')
|
||||
|
||||
// toggle in, state should be maintained
|
||||
toggle.value = true
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe('<p>1</p>')
|
||||
})
|
||||
})
|
||||
|
@ -13,6 +13,8 @@ import { defineComponent } from './apiDefineComponent'
|
||||
import { warn } from './warning'
|
||||
import { ref } from '@vue/reactivity'
|
||||
import { handleError, ErrorCodes } from './errorHandling'
|
||||
import { isKeepAlive } from './components/KeepAlive'
|
||||
import { queueJob } from './scheduler'
|
||||
|
||||
export type AsyncComponentResolveResult<T = Component> = T | { default: T } // es modules
|
||||
|
||||
@ -109,8 +111,14 @@ export function defineAsyncComponent<
|
||||
}
|
||||
|
||||
return defineComponent({
|
||||
__asyncLoader: load,
|
||||
name: 'AsyncComponentWrapper',
|
||||
|
||||
__asyncLoader: load,
|
||||
|
||||
get __asyncResolved() {
|
||||
return resolvedComp
|
||||
},
|
||||
|
||||
setup() {
|
||||
const instance = currentInstance!
|
||||
|
||||
@ -174,6 +182,11 @@ export function defineAsyncComponent<
|
||||
load()
|
||||
.then(() => {
|
||||
loaded.value = true
|
||||
if (instance.parent && isKeepAlive(instance.parent.vnode)) {
|
||||
// parent is keep-alive, force update so the loaded component's
|
||||
// name is taken into account
|
||||
queueJob(instance.parent.update)
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
onError(err)
|
||||
|
@ -189,6 +189,11 @@ export interface ComponentOptionsBase<
|
||||
* @internal
|
||||
*/
|
||||
__asyncLoader?: () => Promise<ConcreteComponent>
|
||||
/**
|
||||
* the inner component resolved by the AsyncComponentWrapper
|
||||
* @internal
|
||||
*/
|
||||
__asyncResolved?: ConcreteComponent
|
||||
/**
|
||||
* cache for merged $options
|
||||
* @internal
|
||||
|
@ -36,6 +36,7 @@ import {
|
||||
import { setTransitionHooks } from './BaseTransition'
|
||||
import { ComponentRenderContext } from '../componentPublicInstance'
|
||||
import { devtoolsComponentAdded } from '../devtools'
|
||||
import { isAsyncWrapper } from '../apiAsyncComponent'
|
||||
|
||||
type MatchPattern = string | RegExp | string[] | RegExp[]
|
||||
|
||||
@ -257,7 +258,15 @@ const KeepAliveImpl: ComponentOptions = {
|
||||
|
||||
let vnode = getInnerChild(rawVNode)
|
||||
const comp = vnode.type as ConcreteComponent
|
||||
const name = getComponentName(comp)
|
||||
|
||||
// for async components, name check should be based in its loaded
|
||||
// inner component if available
|
||||
const name = getComponentName(
|
||||
isAsyncWrapper(vnode)
|
||||
? (vnode.type as ComponentOptions).__asyncResolved || {}
|
||||
: comp
|
||||
)
|
||||
|
||||
const { include, exclude, max } = props
|
||||
|
||||
if (
|
||||
|
Loading…
Reference in New Issue
Block a user