fix: prioritize v-model over value

This commit is contained in:
Alex Snezhko 2025-06-28 16:37:33 -07:00
parent 67ad86174d
commit abc4649c4f
2 changed files with 42 additions and 17 deletions

View File

@ -98,6 +98,24 @@ describe('ssr: v-model', () => {
}"
`)
expect(
compileWithWrapper(
`<select v-model="model" value="2"><option value="1"></option></select>`,
).code,
).toMatchInlineSnapshot(`
"const { ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<div\${
_ssrRenderAttrs(_attrs)
}><select><option value="1"\${
(_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
? _ssrLooseContain(_ctx.model, "1")
: _ssrLooseEqual(_ctx.model, "1"))) ? " selected" : ""
}></option></select></div>\`)
}"
`)
expect(
compileWithWrapper(
`<select multiple v-model="model"><option value="1" selected></option><option value="2"></option></select>`,

View File

@ -82,6 +82,10 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
const needTagForRuntime =
node.tag === 'textarea' || node.tag.indexOf('-') > 0
const hasVModel = node.props.some(
p => p.type === NodeTypes.DIRECTIVE && p.name === 'model',
)
// v-bind="obj", v-bind:[key] and custom directives can potentially
// overwrite other static attrs and can affect final rendering result,
// so when they are present we need to bail out to full `renderAttrs`
@ -141,21 +145,24 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
)
}
} else if (node.tag === 'select') {
// <select> with dynamic v-bind. We don't know if the final props
// will contain .value, so we will have to do something special:
// assign the merged props to a temp variable, and check whether
// it contains value (if yes, mark options selected).
const tempId = `_temp${context.temps++}`
propsExp.arguments = [
createAssignmentExpression(
createSimpleExpression(tempId, false),
mergedProps,
),
]
processSelectChildren(context, node.children, {
type: 'dynamicVBind',
tempId,
})
// v-model takes priority over value
if (!hasVModel) {
// <select> with dynamic v-bind. We don't know if the final props
// will contain .value, so we will have to do something special:
// assign the merged props to a temp variable, and check whether
// it contains value (if yes, mark options selected).
const tempId = `_temp${context.temps++}`
propsExp.arguments = [
createAssignmentExpression(
createSimpleExpression(tempId, false),
mergedProps,
),
]
processSelectChildren(context, node.children, {
type: 'dynamicVBind',
tempId,
})
}
} else if (node.tag === 'input') {
// <input v-bind="obj" v-model>
// we need to determine the props to render for the dynamic v-model
@ -245,7 +252,7 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
node.children = [createInterpolation(prop.exp, prop.loc)]
}
} else if (isTagWithValueBind(node, 'select', prop) && prop.exp) {
if (!needMergeProps) {
if (!needMergeProps && !hasVModel) {
processSelectChildren(context, node.children, {
type: 'dynamicValue',
value: prop.exp,
@ -351,7 +358,7 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
if (node.tag === 'textarea' && name === 'value' && prop.value) {
rawChildrenMap.set(node, escapeHtml(prop.value.content))
} else if (node.tag === 'select' && name === 'value' && prop.value) {
if (!needMergeProps) {
if (!needMergeProps && !hasVModel) {
processSelectChildren(context, node.children, {
type: 'staticValue',
value: prop.value.content,