Skip to content

Commit 854ccce

Browse files
committed
prevent out-in transition to enter early when parent re-renders (fix #3440)
1 parent 6a15602 commit 854ccce

File tree

2 files changed

+62
-4
lines changed

2 files changed

+62
-4
lines changed

Diff for: src/platforms/web/runtime/components/transition.js

+14-4
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ export function extractTransitionData (comp: Component): Object {
4848
return data
4949
}
5050

51+
function placeholder (h, rawChild) {
52+
return /\d-keep-alive$/.test(rawChild.tag)
53+
? h('keep-alive')
54+
: null
55+
}
56+
5157
export default {
5258
name: 'transition',
5359
props: transitionProps,
@@ -101,6 +107,10 @@ export default {
101107
return rawChild
102108
}
103109

110+
if (this._leaving) {
111+
return placeholder(h, rawChild)
112+
}
113+
104114
child.key = child.key == null
105115
? `__v${child.tag + this._uid}__`
106116
: child.key
@@ -115,13 +125,13 @@ export default {
115125

116126
// handle transition mode
117127
if (mode === 'out-in') {
118-
// return empty node and queue update when leave finishes
128+
// return placeholder node and queue update when leave finishes
129+
this._leaving = true
119130
mergeVNodeHook(oldData, 'afterLeave', () => {
131+
this._leaving = false
120132
this.$forceUpdate()
121133
})
122-
return /\d-keep-alive$/.test(rawChild.tag)
123-
? h('keep-alive')
124-
: null
134+
return placeholder(h, rawChild)
125135
} else if (mode === 'in-out') {
126136
let delayedLeave
127137
const performLeave = () => { delayedLeave() }

Diff for: test/unit/features/transition/transition-mode.spec.js

+48
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,54 @@ if (!isIE9) {
9191
}).then(done)
9292
})
9393

94+
// #3440
95+
it('dynamic components, out-in (with extra re-render)', done => {
96+
let next
97+
const vm = new Vue({
98+
template: `<div>
99+
<transition name="test" mode="out-in" @after-leave="afterLeave">
100+
<component :is="view" class="test">
101+
</component>
102+
</transition>
103+
</div>`,
104+
data: { view: 'one' },
105+
components,
106+
methods: {
107+
afterLeave () {
108+
next()
109+
}
110+
}
111+
}).$mount(el)
112+
expect(vm.$el.textContent).toBe('one')
113+
vm.view = 'two'
114+
waitForUpdate(() => {
115+
expect(vm.$el.innerHTML).toBe(
116+
'<div class="test test-leave test-leave-active">one</div><!---->'
117+
)
118+
}).thenWaitFor(nextFrame).then(() => {
119+
expect(vm.$el.innerHTML).toBe(
120+
'<div class="test test-leave-active">one</div><!---->'
121+
)
122+
// Force re-render before the element finishes leaving
123+
// this should not cause the incoming element to enter early
124+
vm.$forceUpdate()
125+
}).thenWaitFor(_next => { next = _next }).then(() => {
126+
expect(vm.$el.innerHTML).toBe('<!---->')
127+
}).thenWaitFor(nextFrame).then(() => {
128+
expect(vm.$el.innerHTML).toBe(
129+
'<div class="test test-enter test-enter-active">two</div>'
130+
)
131+
}).thenWaitFor(nextFrame).then(() => {
132+
expect(vm.$el.innerHTML).toBe(
133+
'<div class="test test-enter-active">two</div>'
134+
)
135+
}).thenWaitFor(duration + 10).then(() => {
136+
expect(vm.$el.innerHTML).toBe(
137+
'<div class="test">two</div>'
138+
)
139+
}).then(done)
140+
})
141+
94142
it('dynamic components, in-out', done => {
95143
let next
96144
const vm = new Vue({

0 commit comments

Comments
 (0)