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 一个组件只能有一个,因为它固定绑定了 value 和 input。如果你想对多个 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>

相关推荐
朱涛的自习室4 小时前
70 美金的 Beyond Compare,我让 Gemini 4 和 DeepSeek 造个开源免费版(Day 2)
android·前端·人工智能
码艺-Alimjan5 小时前
Tauri 源码备份 · 打包 & 解压 总结,源码收集之路
前端·javascript·vue.js
小马不起床5 小时前
零基础玩转 FastAPI_11:前端后台管理页面
前端·状态模式·fastapi
子非鱼a6 小时前
【文件上传】一个普普通通的edu16
前端
美狐美颜SDK开放平台6 小时前
直播APP如何实现美白滤镜功能?视频美颜SDK技术开发方案详解
前端·数据库·人工智能·音视频·美颜sdk·直播美颜sdk·视频美颜sdk
pride.li7 小时前
第03章 Python 教程-基础语法:注释与变量
服务器·前端·后端·python·算法·计算机视觉
屏什么录7 小时前
电脑如何录屏幕视频和声音?从工具到代码,7 种方案帮你彻底搞定
前端·音视频开发
顽疲7 小时前
Java vue 养老系统源码详解:月账单 care_month_bill 与 Quartz 扣费 Job 实践
java·开发语言·vue.js·vue养老·springboot养老·java养老
DevUp7 小时前
标着 JavaScript 的 Wiki 与知识库:既不是 CMS 也不是 SSG(番外三)
javascript·node.js·cms
用户360055579007 小时前
Kestrel_VLLM_CUDA -09 · full-GPU decode:单 token 全设备
前端