Skip to content

Commit c03459b

Browse files
authored
fix(ssr): support client-compiled v-model with dynamic type during ssr (#5787)
fix #5786
1 parent 847d7f7 commit c03459b

File tree

2 files changed

+130
-19
lines changed

2 files changed

+130
-19
lines changed

packages/runtime-dom/src/directives/vModel.ts

+35-19
Original file line numberDiff line numberDiff line change
@@ -269,33 +269,35 @@ export const vModelDynamic: ObjectDirective<
269269
}
270270
}
271271

272-
function callModelHook(
273-
el: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement,
274-
binding: DirectiveBinding,
275-
vnode: VNode,
276-
prevVNode: VNode | null,
277-
hook: keyof ObjectDirective
278-
) {
279-
let modelToUse: ObjectDirective
280-
switch (el.tagName) {
272+
function resolveDynamicModel(tagName: string, type: string | undefined) {
273+
switch (tagName) {
281274
case 'SELECT':
282-
modelToUse = vModelSelect
283-
break
275+
return vModelSelect
284276
case 'TEXTAREA':
285-
modelToUse = vModelText
286-
break
277+
return vModelText
287278
default:
288-
switch (vnode.props && vnode.props.type) {
279+
switch (type) {
289280
case 'checkbox':
290-
modelToUse = vModelCheckbox
291-
break
281+
return vModelCheckbox
292282
case 'radio':
293-
modelToUse = vModelRadio
294-
break
283+
return vModelRadio
295284
default:
296-
modelToUse = vModelText
285+
return vModelText
297286
}
298287
}
288+
}
289+
290+
function callModelHook(
291+
el: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement,
292+
binding: DirectiveBinding,
293+
vnode: VNode,
294+
prevVNode: VNode | null,
295+
hook: keyof ObjectDirective
296+
) {
297+
const modelToUse = resolveDynamicModel(
298+
el.tagName,
299+
vnode.props && vnode.props.type
300+
)
299301
const fn = modelToUse[hook] as DirectiveHook
300302
fn && fn(el, binding, vnode, prevVNode)
301303
}
@@ -324,4 +326,18 @@ export function initVModelForSSR() {
324326
return { checked: true }
325327
}
326328
}
329+
330+
vModelDynamic.getSSRProps = (binding, vnode) => {
331+
if (typeof vnode.type !== 'string') {
332+
return
333+
}
334+
const modelToUse = resolveDynamicModel(
335+
// resolveDynamicModel expects an uppercase tag name, but vnode.type is lowercase
336+
vnode.type.toUpperCase(),
337+
vnode.props && vnode.props.type
338+
)
339+
if (modelToUse.getSSRProps) {
340+
return modelToUse.getSSRProps(binding, vnode)
341+
}
342+
}
327343
}

packages/server-renderer/__tests__/ssrDirectives.spec.ts

+95
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
vModelText,
1212
vModelRadio,
1313
vModelCheckbox,
14+
vModelDynamic,
1415
resolveDirective
1516
} from 'vue'
1617
import { ssrGetDirectiveProps, ssrRenderAttrs } from '../src'
@@ -376,6 +377,100 @@ describe('ssr: directives', () => {
376377
})
377378
})
378379

380+
describe('vnode v-model dynamic', () => {
381+
test('text', async () => {
382+
expect(
383+
await renderToString(
384+
createApp({
385+
render() {
386+
return withDirectives(h('input'), [[vModelDynamic, 'hello']])
387+
}
388+
})
389+
)
390+
).toBe(`<input value="hello">`)
391+
})
392+
393+
test('radio', async () => {
394+
expect(
395+
await renderToString(
396+
createApp({
397+
render() {
398+
return withDirectives(
399+
h('input', { type: 'radio', value: 'hello' }),
400+
[[vModelDynamic, 'hello']]
401+
)
402+
}
403+
})
404+
)
405+
).toBe(`<input type="radio" value="hello" checked>`)
406+
407+
expect(
408+
await renderToString(
409+
createApp({
410+
render() {
411+
return withDirectives(
412+
h('input', { type: 'radio', value: 'hello' }),
413+
[[vModelDynamic, 'foo']]
414+
)
415+
}
416+
})
417+
)
418+
).toBe(`<input type="radio" value="hello">`)
419+
})
420+
421+
test('checkbox', async () => {
422+
expect(
423+
await renderToString(
424+
createApp({
425+
render() {
426+
return withDirectives(h('input', { type: 'checkbox' }), [
427+
[vModelDynamic, true]
428+
])
429+
}
430+
})
431+
)
432+
).toBe(`<input type="checkbox" checked>`)
433+
434+
expect(
435+
await renderToString(
436+
createApp({
437+
render() {
438+
return withDirectives(h('input', { type: 'checkbox' }), [
439+
[vModelDynamic, false]
440+
])
441+
}
442+
})
443+
)
444+
).toBe(`<input type="checkbox">`)
445+
446+
expect(
447+
await renderToString(
448+
createApp({
449+
render() {
450+
return withDirectives(
451+
h('input', { type: 'checkbox', value: 'foo' }),
452+
[[vModelDynamic, ['foo']]]
453+
)
454+
}
455+
})
456+
)
457+
).toBe(`<input type="checkbox" value="foo" checked>`)
458+
459+
expect(
460+
await renderToString(
461+
createApp({
462+
render() {
463+
return withDirectives(
464+
h('input', { type: 'checkbox', value: 'foo' }),
465+
[[vModelDynamic, []]]
466+
)
467+
}
468+
})
469+
)
470+
).toBe(`<input type="checkbox" value="foo">`)
471+
})
472+
})
473+
379474
test('custom directive w/ getSSRProps (vdom)', async () => {
380475
expect(
381476
await renderToString(

0 commit comments

Comments
 (0)