diff --git a/src/core/instance/lifecycle.js b/src/core/instance/lifecycle.js index 6d76ea26b37..34a41964b32 100644 --- a/src/core/instance/lifecycle.js +++ b/src/core/instance/lifecycle.js @@ -116,6 +116,10 @@ export function lifecycleMixin (Vue: Class) { const vm: Component = this const hasChildren = !!(vm.$options._renderChildren || renderChildren) vm.$options._parentVnode = parentVnode + vm.$vnode = parentVnode // update vm's placeholder node without re-render + if (vm._vnode) { // update child tree's parent + vm._vnode.parent = parentVnode + } vm.$options._renderChildren = renderChildren // update props if (propsData && vm.$options.props) { diff --git a/src/core/vdom/patch.js b/src/core/vdom/patch.js index 464d9f6d3e5..98f03fcdb3a 100644 --- a/src/core/vdom/patch.js +++ b/src/core/vdom/patch.js @@ -505,9 +505,13 @@ export function createPatchFunction (backend) { createElm(vnode, insertedVnodeQueue) // component root element replaced. - // update parent placeholder node element. + // update parent placeholder node element, recursively if (vnode.parent) { - vnode.parent.elm = vnode.elm + let ancestor = vnode.parent + while (ancestor) { + ancestor.elm = vnode.elm + ancestor = ancestor.parent + } if (isPatchable(vnode)) { for (let i = 0; i < cbs.create.length; ++i) { cbs.create[i](emptyNode, vnode.parent) diff --git a/test/unit/modules/vdom/patch/edge-cases.spec.js b/test/unit/modules/vdom/patch/edge-cases.spec.js index 333e010c42c..5d374deae26 100644 --- a/test/unit/modules/vdom/patch/edge-cases.spec.js +++ b/test/unit/modules/vdom/patch/edge-cases.spec.js @@ -57,4 +57,61 @@ describe('vdom patch: edge cases', () => { expect(vm.$el.querySelector('.d').textContent).toBe('2') }).then(done) }) + + it('should synchronize vm\' vnode', done => { + const comp = { + data: () => ({ swap: true }), + render (h) { + return this.swap + ? h('a', 'atag') + : h('span', 'span') + } + } + + const wrapper = { + render: h => h('comp'), + components: { comp } + } + + const vm = new Vue({ + render (h) { + const children = [ + h('wrapper'), + h('div', 'row') + ] + if (this.swap) { + children.reverse() + } + return h('div', children) + }, + data: () => ({ swap: false }), + components: { wrapper } + }).$mount() + + expect(vm.$el.innerHTML).toBe('atag
row
') + const wrapperVm = vm.$children[0] + const compVm = wrapperVm.$children[0] + vm.swap = true + waitForUpdate(() => { + expect(compVm.$vnode.parent).toBe(wrapperVm.$vnode) + expect(vm.$el.innerHTML).toBe('
row
atag') + vm.swap = false + }) + .then(() => { + expect(compVm.$vnode.parent).toBe(wrapperVm.$vnode) + expect(vm.$el.innerHTML).toBe('atag
row
') + compVm.swap = false + }) + .then(() => { + expect(vm.$el.innerHTML).toBe('span
row
') + expect(compVm.$vnode.parent).toBe(wrapperVm.$vnode) + vm.swap = true + }) + .then(() => { + expect(vm.$el.innerHTML).toBe('
row
span') + expect(compVm.$vnode.parent).toBe(wrapperVm.$vnode) + vm.swap = true + }) + .then(done) + }) })