Skip to content

Commit

Permalink
fix(ssr): properly hydrate non-string value bindings
Browse files Browse the repository at this point in the history
fix #4006
  • Loading branch information
yyx990803 committed Jul 1, 2021
1 parent fded1e8 commit 34d4991
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 4 deletions.
36 changes: 35 additions & 1 deletion packages/runtime-core/__tests__/hydration.spec.ts
Expand Up @@ -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')
Expand Down Expand Up @@ -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')
Expand Down
13 changes: 10 additions & 3 deletions packages/runtime-core/src/hydration.ts
Expand Up @@ -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])
}
}
Expand Down

0 comments on commit 34d4991

Please sign in to comment.