Skip to content

Commit e448fd4

Browse files
committed
fix(transition): consider async placeholder as valid child to return, fix #6256
1 parent 3cac5c7 commit e448fd4

File tree

2 files changed

+100
-3
lines changed

2 files changed

+100
-3
lines changed

src/platforms/web/runtime/components/transition.js

+16-3
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
// supports transition mode (out-in / in-out)
55

66
import { warn } from 'core/util/index'
7-
import { camelize, extend, isPrimitive } from 'shared/util'
8-
import { mergeVNodeHook, getFirstComponentChild } from 'core/vdom/helpers/index'
7+
import { camelize, extend, isPrimitive, isDef } from 'shared/util'
8+
import { mergeVNodeHook } from 'core/vdom/helpers/index'
99

1010
export const transitionProps = {
1111
name: String,
@@ -25,12 +25,25 @@ export const transitionProps = {
2525
duration: [Number, String, Object]
2626
}
2727

28+
// similar with getFirstComponentChild
29+
// but consider async placeholder as valid child
30+
function getFirstValidChild (children: ?Array<VNode>): ?VNode {
31+
if (Array.isArray(children)) {
32+
for (let i = 0; i < children.length; i++) {
33+
const c = children[i]
34+
if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {
35+
return c
36+
}
37+
}
38+
}
39+
}
40+
2841
// in case the child is also an abstract component, e.g. <keep-alive>
2942
// we want to recursively retrieve the real component to be rendered
3043
function getRealChild (vnode: ?VNode): ?VNode {
3144
const compOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
3245
if (compOptions && compOptions.Ctor.options.abstract) {
33-
return getRealChild(getFirstComponentChild(compOptions.children))
46+
return getRealChild(getFirstValidChild(compOptions.children))
3447
} else {
3548
return vnode
3649
}

test/unit/features/component/component-keep-alive.spec.js

+84
Original file line numberDiff line numberDiff line change
@@ -862,5 +862,89 @@ describe('Component keep-alive', () => {
862862
)
863863
}).then(done)
864864
})
865+
866+
it('async components with transition-mode out-in', done => {
867+
const barResolve = jasmine.createSpy('bar resolved')
868+
let next
869+
const foo = (resolve) => {
870+
setTimeout(() => {
871+
resolve(one)
872+
Vue.nextTick(next)
873+
}, duration / 2)
874+
}
875+
const bar = (resolve) => {
876+
setTimeout(() => {
877+
resolve(two)
878+
barResolve()
879+
}, duration / 2)
880+
}
881+
components = {
882+
foo,
883+
bar
884+
}
885+
const vm = new Vue({
886+
template: `<div>
887+
<transition name="test" mode="out-in" @after-enter="afterEnter" @after-leave="afterLeave">
888+
<keep-alive>
889+
<component :is="view" class="test"></component>
890+
</keep-alive>
891+
</transition>
892+
</div>`,
893+
data: {
894+
view: 'foo'
895+
},
896+
components,
897+
methods: {
898+
afterEnter () {
899+
next()
900+
},
901+
afterLeave () {
902+
next()
903+
}
904+
}
905+
}).$mount(el)
906+
expect(vm.$el.textContent).toBe('')
907+
next = () => {
908+
assertHookCalls(one, [1, 1, 1, 0, 0])
909+
assertHookCalls(two, [0, 0, 0, 0, 0])
910+
waitForUpdate(() => {
911+
expect(vm.$el.innerHTML).toBe(
912+
'<div class="test test-enter test-enter-active">one</div>'
913+
)
914+
}).thenWaitFor(nextFrame).then(() => {
915+
expect(vm.$el.innerHTML).toBe(
916+
'<div class="test test-enter-active test-enter-to">one</div>'
917+
)
918+
}).thenWaitFor(_next => { next = _next }).then(() => {
919+
// foo afterEnter get called
920+
expect(vm.$el.innerHTML).toBe('<div class="test">one</div>')
921+
vm.view = 'bar'
922+
}).thenWaitFor(nextFrame).then(() => {
923+
assertHookCalls(one, [1, 1, 1, 1, 0])
924+
assertHookCalls(two, [0, 0, 0, 0, 0])
925+
expect(vm.$el.innerHTML).toBe(
926+
'<div class="test test-leave-active test-leave-to">one</div><!---->'
927+
)
928+
}).thenWaitFor(_next => { next = _next }).then(() => {
929+
// foo afterLeave get called
930+
// and bar has already been resolved before afterLeave get called
931+
expect(barResolve.calls.count()).toBe(1)
932+
expect(vm.$el.innerHTML).toBe('<!---->')
933+
}).thenWaitFor(nextFrame).then(() => {
934+
expect(vm.$el.innerHTML).toBe(
935+
'<div class="test test-enter test-enter-active">two</div>'
936+
)
937+
assertHookCalls(one, [1, 1, 1, 1, 0])
938+
assertHookCalls(two, [1, 1, 1, 0, 0])
939+
}).thenWaitFor(nextFrame).then(() => {
940+
expect(vm.$el.innerHTML).toBe(
941+
'<div class="test test-enter-active test-enter-to">two</div>'
942+
)
943+
}).thenWaitFor(_next => { next = _next }).then(() => {
944+
// bar afterEnter get called
945+
expect(vm.$el.innerHTML).toBe('<div class="test">two</div>')
946+
}).then(done)
947+
}
948+
})
865949
}
866950
})

0 commit comments

Comments
 (0)