Skip to content

Commit 34d4991

Browse files
committed
fix(ssr): properly hydrate non-string value bindings
fix #4006
1 parent fded1e8 commit 34d4991

File tree

2 files changed

+45
-4
lines changed

2 files changed

+45
-4
lines changed

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

+35-1
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,13 @@ import {
1010
onMounted,
1111
defineAsyncComponent,
1212
defineComponent,
13-
createTextVNode
13+
createTextVNode,
14+
createVNode,
15+
withDirectives,
16+
vModelCheckbox
1417
} from '@vue/runtime-dom'
1518
import { renderToString, SSRContext } from '@vue/server-renderer'
19+
import { PatchFlags } from '../../shared/src'
1620

1721
function mountWithHydration(html: string, render: () => any) {
1822
const container = document.createElement('div')
@@ -761,6 +765,36 @@ describe('SSR hydration', () => {
761765
)
762766
})
763767

768+
test('force hydrate input v-model with non-string value bindings', () => {
769+
const { container } = mountWithHydration(
770+
'<input type="checkbox" value="true">',
771+
() =>
772+
withDirectives(
773+
createVNode(
774+
'input',
775+
{ type: 'checkbox', 'true-value': true },
776+
null,
777+
PatchFlags.PROPS,
778+
['true-value']
779+
),
780+
[[vModelCheckbox, true]]
781+
)
782+
)
783+
expect((container.firstChild as any)._trueValue).toBe(true)
784+
})
785+
786+
test('force hydrate select option with non-string value bindings', () => {
787+
const { container } = mountWithHydration(
788+
'<select><option :value="true">ok</option></select>',
789+
() =>
790+
h('select', [
791+
// hoisted because bound value is a constant...
792+
createVNode('option', { value: true }, null, -1 /* HOISTED */)
793+
])
794+
)
795+
expect((container.firstChild!.firstChild as any)._value).toBe(true)
796+
})
797+
764798
describe('mismatch handling', () => {
765799
test('text node', () => {
766800
const { container } = mountWithHydration(`foo`, () => 'bar')

packages/runtime-core/src/hydration.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -264,21 +264,28 @@ export function createHydrationFunctions(
264264
optimized: boolean
265265
) => {
266266
optimized = optimized || !!vnode.dynamicChildren
267-
const { props, patchFlag, shapeFlag, dirs } = vnode
267+
const { type, props, patchFlag, shapeFlag, dirs } = vnode
268+
// #4006 for form elements with non-string v-model value bindings
269+
// e.g. <option :value="obj">, <input type="checkbox" :true-value="1">
270+
const forcePatchValue = (type === 'input' && dirs) || type === 'option'
268271
// skip props & children if this is hoisted static nodes
269-
if (patchFlag !== PatchFlags.HOISTED) {
272+
if (forcePatchValue || patchFlag !== PatchFlags.HOISTED) {
270273
if (dirs) {
271274
invokeDirectiveHook(vnode, null, parentComponent, 'created')
272275
}
273276
// props
274277
if (props) {
275278
if (
279+
forcePatchValue ||
276280
!optimized ||
277281
(patchFlag & PatchFlags.FULL_PROPS ||
278282
patchFlag & PatchFlags.HYDRATE_EVENTS)
279283
) {
280284
for (const key in props) {
281-
if (!isReservedProp(key) && isOn(key)) {
285+
if (
286+
(forcePatchValue && key.endsWith('value')) ||
287+
(isOn(key) && !isReservedProp(key))
288+
) {
282289
patchProp(el, key, null, props[key])
283290
}
284291
}

0 commit comments

Comments
 (0)