浅谈 Vue 的 v-model

5 min

在 Vue.js 开发中,表单数据的双向绑定是一个常见需求。v-model 作为 Vue 提供的语法糖,能够简洁高效地实现数据与表单元素的动态同步。它不仅能用于原生表单控件,还能在自定义组件中灵活应用,大大简化了状态管理的复杂度。本文将深入解析 v-model 的工作原理、使用方式,以及如何在自定义组件中实现双向绑定,帮助开发者更高效地处理表单交互逻辑。

1. v-model 简介

你可以用 v-model 指令在表单 <input><textarea><select> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model 本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。 —— 官方文档

v-model 是一个语法糖,它本质上是一个双向绑定的快捷方式。

2. v-model 的原理

2.1 双向绑定

在前端处理表单时,我们常常需要将表单输入框的内容同步给 JavaScript 中相应的变量。这需要做两个工作:

  1. 将变量绑定到输入框
  2. 当输入框输入时,用输入的值更新变量值

如果只绑定变量(仅第一个工作):

<input :value="text">

这种情况下,输入框将始终显示 text 变量的初始值,因为没有事件处理来改变它的值。因此,想要输入框显示输入的值时,应该添加事件处理来同步更新变量值(通常选择 input 事件)。那么代码将调整为如下:

<input
  :value="text"
  @input="event => text = event.target.value">

这样就是实现了输入值 - 变量值的双向绑定:当输入值改变时会同步更新变量值(事件触发);当变量值改变时也会同步到输入框(input 的 value 属性)。

2.2 v-model

使用上述的方式,我们可以手动实现一个双向绑定的输入框组件,但是这种方式比较繁琐(每次都要手动编写事件处理逻辑)。所有,vue 提供了 v-model 来简化这种操作,使用方式如下:

<input v-model="text">

通过简单的 v-model 指令即可实现双向绑定功能。

Vue 添加了对不同表单元素的支持:

  • 文本类型的的 <input><textarea> 元素会绑定 value property 并侦听 input 事件;
  • <input type="checkbox"><input type="radio"> 会绑定 checked property 并侦听 change 事件;
  • <select> 会绑定 value property 并侦听 change 事件。

2.3 v-model 的底层机制

当使用 v-model 时,编译器会将 v-model 进行展开为 value + event 的方式:

<input v-model="text">

将被展开为

<input
  :value="text"
  @input="event => text = event.target.value">

所有以上两种方式是等价的,而 v-model 只是一种语法糖。

3. 自定义组件的 v-model

Vue 只是针对表单元素添加了 v-model 机制,对于我们自定义的组件,需要手动实现 v-model 的机制来实现。

根据上面介绍的原理,我们在自定义组件时如果想要为组件添加 v-model 支持,只需要完成两件事:

  1. 为组件创建一个 modelValue 用来接收 v-model 绑定的值
  2. 自定义一个事件当内容改变时同步到 modelValue

例如,定义一个组件如下:

<!-- MyComponent.vue -->
<script>
export default {
  model: {
    prop: 'title',
    event: 'update'
  },
  props:{
    title: String
  },
}
</script>

<template>
  <input
    type="text"
    :value="title"
    @input="$emit('update', $event.target.value)"
  />
</template>

在使用时:

<MyComponent v-model="bookTitle" />

组件会被编译展开为如下等价格式:

<MyComponent :title="bookTitle" @update="value => bookTitle = value" />

当然也可以通过这种方式来使用,只是比较繁琐而已。

根据这种等价格式我们可以反推,在自定义组件时如何定义 modelValue 和更新事件。