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 中,我们有两种方式来实现这种"双向绑定"的错觉:

  1. v-model :主要用于原生的 input 和自定义表单组件,默认绑定 value prop 和 input 事件。
  2. .sync 修饰符:用于任何需要"双向绑定" prop 的场景,语法更灵活。

Vue 3 对 v-model 进行了重大更新和增强,使其可以完全替代 .sync 修饰符的功能,让 API 更加统一和简洁。本文将带你彻底理解它们的演变过程和使用方法。


一、Vue 2 中的 v-model 与 .sync

1. Vue 2 的 v-model

在 Vue 2 中,v-model 本质上是一个语法糖。它在组件上的应用等同于:

html 复制代码
<!-- 父组件 -->
<ChildComponent v-model="pageTitle" />

<!-- 等价于 -->
<ChildComponent :value="pageTitle" @input="pageTitle = $event" />

这意味着,在子组件内部,你需要:

  1. 接收一个名为 value 的 prop。
  2. 当需要更新值时,触发一个名为 input 的事件。

子组件 (ChildComponent.vue) 实现:

html 复制代码
<template>
  <input
    type="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 一个组件只能有一个,因为它固定绑定了 valueinput。如果你想对多个 prop 进行"双向绑定",就需要用到 .sync 修饰符。

.sync 也是一个语法糖:

html 复制代码
<!-- 父组件 -->
<ChildComponent :title.sync="pageTitle" />

<!-- 等价于 -->
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />

在子组件内部,你需要:

  1. 接收相应的 prop(例如 title)。
  2. 当需要更新时,触发一个格式为 update:propName 的事件。

子组件 (ChildComponent.vue) 实现:

html 复制代码
<template>
  <input
    type="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,等价于:

html 复制代码
<!-- 父组件 -->
<ChildComponent v-model="pageTitle" />

<!-- 等价于 -->
<ChildComponent :modelValue="pageTitle" @update:modelValue="pageTitle = $event" />

主要变化:

  • Prop 名 :从 value 变为 modelValue
  • 事件名 :从 input 变为 update:modelValue

子组件 (ChildComponent.vue) 需要相应调整:

html 复制代码
<template>
  <input
    type="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 的功能。

父组件用法:

html 复制代码
<!-- 父组件 -->
<UserName
  v-model:first-name="firstName"
  v-model:last-name="lastName"
/>

<!-- 在 Vue 2 中,你可能会写成 -->
<UserName
  :first-name.sync="firstName"
  :last-name.sync="lastName"
/>

这里的每一个 v-model:arg 都等价于:

html 复制代码
<UserName
  :first-name="firstName"
  @update:first-name="firstName = $event"
  :last-name="lastName"
  @update:last-name="lastName = $event"
/>

子组件 (UserName.vue) 的实现:

html 复制代码
<template>
  <input
    type="text"
    :value="firstName"
    @input="$emit('update:firstName', $event.target.value)"
  />
  <input
    type="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,这在处理原生表单控件时非常有用。

子组件示例:

html 复制代码
<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)。

父组件:

html 复制代码
<MyComponent v-model.capitalize="myText" />

子组件:

html 复制代码
<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>

相关推荐
passerby60615 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了5 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅6 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅6 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅6 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment6 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅7 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊7 小时前
jwt介绍
前端
爱敲代码的小鱼7 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
吹牛不交税7 小时前
admin.net-v2 框架使用笔记-netcore8.0/10.0版
vue.js·.netcore