v-model与-sync的演变和融合
Vue 2 到 Vue 3:一篇文章搞懂 v-model 和 .sync 的演变与融合
前言:为什么我们需要 v-model 和 .sync?
在 Vue 应用开发中,组件通信是核心知识之一。其中,父组件向子组件传递数据通过 props
实现,而子组件向父组件通知事件则通过 $emit
实现。这是 Vue 数据流的基础。
但在某些场景下,我们会对一个 prop
进行“双向绑定”,这通常发生在封装表单组件或可交互的 UI 控件时。例如:
- 父组件传递一个
title
给子组件。 - 子组件在一个
input
框中显示这个title
。 - 当用户在子组件的
input
框中输入内容时,我们需要能同步地更新父组件中的title
。
在 Vue 2 中,我们有两种方式来实现这种“双向绑定”的错觉:
v-model
:主要用于原生的 input 和自定义表单组件,默认绑定value
prop 和input
事件。.sync
修饰符:用于任何需要“双向绑定” prop 的场景,语法更灵活。
而 Vue 3 对 v-model
进行了重大更新和增强,使其可以完全替代 .sync
修饰符的功能,让 API 更加统一和简洁。本文将带你彻底理解它们的演变过程和使用方法。
一、Vue 2 中的 v-model 与 .sync
1. Vue 2 的 v-model
在 Vue 2 中,v-model
本质上是一个语法糖。它在组件上的应用等同于:
<!-- 父组件 -->
<ChildComponent v-model="pageTitle" /><!-- 等价于 -->
<ChildComponent :value="pageTitle" @input="pageTitle = $event" />
这意味着,在子组件内部,你需要:
- 接收一个名为
value
的 prop。 - 当需要更新值时,触发一个名为
input
的事件。
子组件 (ChildComponent.vue) 实现:
<template><inputtype="text":value="value" <!-- 接收父组件传来的 value -->@input="$emit('input', $event.target.value)" <!-- 输入时触发 input 事件 -->/>
</template><script>
export default {props: ['value'] // 声明 value prop
}
</script>
2. Vue 2 的 .sync 修饰符
v-model
一个组件只能有一个,因为它固定绑定了 value
和 input
。如果你想对多个 prop 进行“双向绑定”,就需要用到 .sync
修饰符。
.sync
也是一个语法糖:
<!-- 父组件 -->
<ChildComponent :title.sync="pageTitle" /><!-- 等价于 -->
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
在子组件内部,你需要:
- 接收相应的 prop(例如
title
)。 - 当需要更新时,触发一个格式为
update:propName
的事件。
子组件 (ChildComponent.vue) 实现:
<template><inputtype="text":value="title" <!-- 接收父组件传来的 title -->@input="$emit('update:title', $event.target.value)" <!-- 触发 update:title 事件 -->/>
</template><script>
export default {props: ['title'] // 声明 title prop
}
</script>
小结 Vue 2:
v-model
:一对一(一个组件一个),固定value
prop 和input
事件。.sync
:一对多(一个组件多个),灵活的自定义 prop 和update:propName
事件。
二、Vue 3 中的统一:v-model 的进化
Vue 3 为了减少概念,简化 API,对 v-model
进行了重塑。v-model
不再固定使用 value
作为 prop 和 input
作为事件,并且直接取代了 .sync
修饰符的功能。
1. 默认行为的变化
在 Vue 3 的组件上使用 v-model
,等价于:
<!-- 父组件 -->
<ChildComponent v-model="pageTitle" /><!-- 等价于 -->
<ChildComponent :modelValue="pageTitle" @update:modelValue="pageTitle = $event" />
主要变化:
- Prop 名:从
value
变为modelValue
。 - 事件名:从
input
变为update:modelValue
。
子组件 (ChildComponent.vue) 需要相应调整:
<template><inputtype="text":value="modelValue" <!-- 接收 modelValue -->@input="$emit('update:modelValue', $event.target.value)" <!-- 触发 update:modelValue -->/>
</template><script>
export default {props: ['modelValue'],emits: ['update:modelValue']
}
</script>
2. 如何替代 .sync?使用多个 v-model!
这是 Vue 3 最精彩的改进。你现在可以在一个组件上绑定多个 v-model
,从而实现之前 .sync
的功能。
父组件用法:
<!-- 父组件 -->
<UserNamev-model:first-name="firstName"v-model:last-name="lastName"
/><!-- 在 Vue 2 中,你可能会写成 -->
<UserName:first-name.sync="firstName":last-name.sync="lastName"
/>
这里的每一个 v-model:arg
都等价于:
<UserName:first-name="firstName"@update:first-name="firstName = $event":last-name="lastName"@update:last-name="lastName = $event"
/>
子组件 (UserName.vue) 的实现:
<template><inputtype="text":value="firstName"@input="$emit('update:firstName', $event.target.value)"/><inputtype="text":value="lastName"@input="$emit('update:lastName', $event.target.value)"/>
</template><script>
export default {props: {firstName: String,lastName: String},emits: ['update:firstName', 'update:lastName']
}
</script>
3. 对比总结:Vue 2 与 Vue 3
特性 | Vue 2 | Vue 3 |
---|---|---|
单个数据绑定 | v-model="title" | v-model="title" |
等价于 | :value="title" @input="title = $event" | :modelValue="title" @update:modelValue="title = $event" |
多个数据绑定 | :title.sync="title" | v-model:title="title" |
等价于 | :title="title" @update:title="title = $event" | :title="title" @update:title="title = $event" |
主要优势 | 语法分离,意图明确 | API 统一,语法精简,一个 v-model 规则走天下 |
三、最佳实践与技巧
1. 在自定义组件中处理 v-model
你可以在子组件中定义一个计算属性,通过 getter 和 setter 来优雅地处理 v-model
,这在处理原生表单控件时非常有用。
子组件示例:
<template><input type="text" v-model="internalValue" />
</template><script>
export default {props: ['modelValue'],emits: ['update:modelValue'],computed: {internalValue: {get() {return this.modelValue;},set(value) {this.$emit('update:modelValue', value);}}}
}
</script>
2. 自定义修饰符
Vue 3 的 v-model
支持自定义修饰符。父组件使用 v-model.modifier
,子组件可以在 props
中接收到一个名为 modelModifiers
的对象(对于带参数的 v-model:arg
,则是 argModifiers
)。
父组件:
<MyComponent v-model.capitalize="myText" />
子组件:
<script>
export default {props: {modelValue: String,modelModifiers: { // 接收修饰符对象default: () => ({})}},emits: ['update:modelValue'],methods: {emitValue(e) {let value = e.target.value;// 检查是否有 capitalize 修饰符if (this.modelModifiers.capitalize) {value = value.charAt(0).toUpperCase() + value.slice(1);}this.$emit('update:modelValue', value);}}
}
</script>