Переглянути джерело

relax SSR hydration match check to allow client populating empty parent nodes

Evan You 9 роки тому
батько
коміт
7a2c986722
2 змінених файлів з 61 додано та 25 видалено
  1. 32 23
      src/core/vdom/patch.js
  2. 29 2
      test/unit/modules/vdom/patch/hydration.spec.js

+ 32 - 23
src/core/vdom/patch.js

@@ -117,13 +117,7 @@ export function createPatchFunction (backend) {
         ? nodeOps.createElementNS(vnode.ns, tag)
         : nodeOps.createElement(tag)
       setScope(vnode)
-      if (Array.isArray(children)) {
-        for (i = 0; i < children.length; ++i) {
-          nodeOps.appendChild(elm, createElm(children[i], insertedVnodeQueue, true))
-        }
-      } else if (isPrimitive(vnode.text)) {
-        nodeOps.appendChild(elm, nodeOps.createTextNode(vnode.text))
-      }
+      createChildren(vnode, children, insertedVnodeQueue)
       if (isDef(data)) {
         invokeCreateHooks(vnode, insertedVnodeQueue)
       }
@@ -135,6 +129,16 @@ export function createPatchFunction (backend) {
     return vnode.elm
   }
 
+  function createChildren (vnode, children, insertedVnodeQueue) {
+    if (Array.isArray(children)) {
+      for (let i = 0; i < children.length; ++i) {
+        nodeOps.appendChild(vnode.elm, createElm(children[i], insertedVnodeQueue, true))
+      }
+    } else if (isPrimitive(vnode.text)) {
+      nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(vnode.text))
+    }
+  }
+
   function isPatchable (vnode) {
     while (vnode.child) {
       vnode = vnode.child._vnode
@@ -404,26 +408,31 @@ export function createPatchFunction (backend) {
     if (isDef(tag)) {
       if (isDef(children)) {
         const childNodes = nodeOps.childNodes(elm)
-        let childrenMatch = true
-        if (childNodes.length !== children.length) {
-          childrenMatch = false
+        // empty element, allow client to pick up and populate children
+        if (!childNodes.length) {
+          createChildren(vnode, children, insertedVnodeQueue)
         } else {
-          for (let i = 0; i < children.length; i++) {
-            if (!hydrate(childNodes[i], children[i], insertedVnodeQueue)) {
-              childrenMatch = false
-              break
+          let childrenMatch = true
+          if (childNodes.length !== children.length) {
+            childrenMatch = false
+          } else {
+            for (let i = 0; i < children.length; i++) {
+              if (!hydrate(childNodes[i], children[i], insertedVnodeQueue)) {
+                childrenMatch = false
+                break
+              }
             }
           }
-        }
-        if (!childrenMatch) {
-          if (process.env.NODE_ENV !== 'production' &&
-              typeof console !== 'undefined' &&
-              !bailed) {
-            bailed = true
-            console.warn('Parent: ', elm)
-            console.warn('Mismatching childNodes vs. VNodes: ', childNodes, children)
+          if (!childrenMatch) {
+            if (process.env.NODE_ENV !== 'production' &&
+                typeof console !== 'undefined' &&
+                !bailed) {
+              bailed = true
+              console.warn('Parent: ', elm)
+              console.warn('Mismatching childNodes vs. VNodes: ', childNodes, children)
+            }
+            return false
           }
-          return false
         }
       }
       if (isDef(data)) {

+ 29 - 2
test/unit/modules/vdom/patch/hydration.spec.js

@@ -141,9 +141,36 @@ describe('vdom patch: hydration', () => {
           template: '<div><span>{{a}}</span></div>'
         }
       }
-    })
+    }).$mount(dom)
 
-    vm.$mount(dom)
     expect('not matching server-rendered content').toHaveBeenWarned()
   })
+
+  it('should pick up elements with no children and populate without warning', done => {
+    const dom = document.createElement('div')
+    dom.setAttribute('server-rendered', 'true')
+    dom.innerHTML = '<div><span></span></div>'
+    const span = dom.querySelector('span')
+
+    const vm = new Vue({
+      template: '<div><test></test></div>',
+      components: {
+        test: {
+          data () {
+            return { a: 'qux' }
+          },
+          template: '<div><span>{{a}}</span></div>'
+        }
+      }
+    }).$mount(dom)
+
+    expect('not matching server-rendered content').not.toHaveBeenWarned()
+    expect(span).toBe(vm.$el.querySelector('span'))
+    expect(vm.$el.innerHTML).toBe('<div><span>qux</span></div>')
+
+    vm.$children[0].a = 'foo'
+    waitForUpdate(() => {
+      expect(vm.$el.innerHTML).toBe('<div><span>foo</span></div>')
+    }).then(done)
+  })
 })