]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(ssr): properly hydrate non-string value bindings
authorEvan You <yyx990803@gmail.com>
Thu, 1 Jul 2021 21:11:23 +0000 (17:11 -0400)
committerEvan You <yyx990803@gmail.com>
Thu, 1 Jul 2021 21:11:23 +0000 (17:11 -0400)
fix #4006

packages/runtime-core/__tests__/hydration.spec.ts
packages/runtime-core/src/hydration.ts

index c55a9ab6104b190b1497fed02387d1be6b5128eb..ffe54882b5208fe7d8a3c475a941fcb53fbbdf91 100644 (file)
@@ -10,9 +10,13 @@ import {
   onMounted,
   defineAsyncComponent,
   defineComponent,
-  createTextVNode
+  createTextVNode,
+  createVNode,
+  withDirectives,
+  vModelCheckbox
 } from '@vue/runtime-dom'
 import { renderToString, SSRContext } from '@vue/server-renderer'
+import { PatchFlags } from '../../shared/src'
 
 function mountWithHydration(html: string, render: () => any) {
   const container = document.createElement('div')
@@ -761,6 +765,36 @@ describe('SSR hydration', () => {
     )
   })
 
+  test('force hydrate input v-model with non-string value bindings', () => {
+    const { container } = mountWithHydration(
+      '<input type="checkbox" value="true">',
+      () =>
+        withDirectives(
+          createVNode(
+            'input',
+            { type: 'checkbox', 'true-value': true },
+            null,
+            PatchFlags.PROPS,
+            ['true-value']
+          ),
+          [[vModelCheckbox, true]]
+        )
+    )
+    expect((container.firstChild as any)._trueValue).toBe(true)
+  })
+
+  test('force hydrate select option with non-string value bindings', () => {
+    const { container } = mountWithHydration(
+      '<select><option :value="true">ok</option></select>',
+      () =>
+        h('select', [
+          // hoisted because bound value is a constant...
+          createVNode('option', { value: true }, null, -1 /* HOISTED */)
+        ])
+    )
+    expect((container.firstChild!.firstChild as any)._value).toBe(true)
+  })
+
   describe('mismatch handling', () => {
     test('text node', () => {
       const { container } = mountWithHydration(`foo`, () => 'bar')
index f4f7ea4795ac8fc7d33645d48d6eae2771e4e79f..94c221db1950e641498750c2af2dbe6da335b772 100644 (file)
@@ -264,21 +264,28 @@ export function createHydrationFunctions(
     optimized: boolean
   ) => {
     optimized = optimized || !!vnode.dynamicChildren
-    const { props, patchFlag, shapeFlag, dirs } = vnode
+    const { type, props, patchFlag, shapeFlag, dirs } = vnode
+    // #4006 for form elements with non-string v-model value bindings
+    // e.g. <option :value="obj">, <input type="checkbox" :true-value="1">
+    const forcePatchValue = (type === 'input' && dirs) || type === 'option'
     // skip props & children if this is hoisted static nodes
-    if (patchFlag !== PatchFlags.HOISTED) {
+    if (forcePatchValue || patchFlag !== PatchFlags.HOISTED) {
       if (dirs) {
         invokeDirectiveHook(vnode, null, parentComponent, 'created')
       }
       // props
       if (props) {
         if (
+          forcePatchValue ||
           !optimized ||
           (patchFlag & PatchFlags.FULL_PROPS ||
             patchFlag & PatchFlags.HYDRATE_EVENTS)
         ) {
           for (const key in props) {
-            if (!isReservedProp(key) && isOn(key)) {
+            if (
+              (forcePatchValue && key.endsWith('value')) ||
+              (isOn(key) && !isReservedProp(key))
+            ) {
               patchProp(el, key, null, props[key])
             }
           }