فهرست منبع

Ensure Vue instance's vnode and element is up to date (#4299)

* fix #4284, recursively update vnode element

* fix #4284, ensure vm's vnode is up to date

* add test for edge case
(´・ω・`) 9 سال پیش
والد
کامیت
fce3f04a44
3فایلهای تغییر یافته به همراه67 افزوده شده و 2 حذف شده
  1. 4 0
      src/core/instance/lifecycle.js
  2. 6 2
      src/core/vdom/patch.js
  3. 57 0
      test/unit/modules/vdom/patch/edge-cases.spec.js

+ 4 - 0
src/core/instance/lifecycle.js

@@ -116,6 +116,10 @@ export function lifecycleMixin (Vue: Class<Component>) {
     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) {

+ 6 - 2
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)

+ 57 - 0
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('<a>atag</a><div>row</div>')
+    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('<div>row</div><a>atag</a>')
+      vm.swap = false
+    })
+    .then(() => {
+      expect(compVm.$vnode.parent).toBe(wrapperVm.$vnode)
+      expect(vm.$el.innerHTML).toBe('<a>atag</a><div>row</div>')
+      compVm.swap = false
+    })
+    .then(() => {
+      expect(vm.$el.innerHTML).toBe('<span>span</span><div>row</div>')
+      expect(compVm.$vnode.parent).toBe(wrapperVm.$vnode)
+      vm.swap = true
+    })
+    .then(() => {
+      expect(vm.$el.innerHTML).toBe('<div>row</div><span>span</span>')
+      expect(compVm.$vnode.parent).toBe(wrapperVm.$vnode)
+      vm.swap = true
+    })
+    .then(done)
+  })
 })