v-model 在 Vue2 和 Vue3 中的区别

v-model 是 Vue 中使用频率特别高的一个指令,而 Vue3 中的 v-model 有了很大的变化,本文将详细讲述一下 Vue2 和 Vue3 中的 v-model 的区别。javascript

Vue2 中的 v-model

若是对 Vue2 中的语法很熟悉,这部分能够不看。html

首先来回顾一下 Vue2 中的 v-model,它主要用于表单元素和自定义组件上。v-model本质上是一个语法糖,会对用户的输入作一些特殊处理以达到更新数据,而所谓的处理其实就是给使用的元素默认绑定属性和事件。java

v-model 使用在表单元素上时,会根据元素的不一样而采用不一样的处理:api

  • <input type="text">文本<textarea>上使用时,会默认给元素绑定名为 value 的 prop 和名为 input 的事件;
  • <input type="checkbox">复选框<input type="radio">单选框 上使用时,会默认绑定名为 checked 的 prop 和名为 change 的事件;
  • <select>选择框 上使用时,则绑定名为 value 的 prop 和名为 change 的事件。

这些是 Vue 默认帮咱们处理的,能够直接使用。可是你也会发现一些第三方组件也能够使用 v-model ,好比 Element 中的 Input 组件。这是由于这些组件本身实现了 v-model,原理其实就是上面说到的绑定属性和事件。markdown

咱们能够尝试实现一下 v-model,来开发一个简单的输入组件,就叫 MyInput 吧:ui

<!-- MyInput 组件代码 -->

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

<script> export default { props: { value: String, // 默认接收一个名为 value 的 prop } } </script>
复制代码

上面代码就实现了组件的 v-model 功能,当在这个组件上使用 v-model 时:this

<my-input v-model="msg"></my-input>
复制代码

其实就等同于:spa

<my-input :value="msg" @input="msg = $event">
复制代码

Vue 还提供了 model 选项,用于将属性或事件名称改成其余名称,好比上面的 MyInput 组件,咱们改一下:code

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

<script> export default { model: { prop: "title", // 将默认的 prop 名 value 改成 title event: "change", // 将默认的事件名 input 改成 change }, props: { title: String, // 注意 template 代码中也要修改成 title }, }; </script>
复制代码

此时使用组件:component

<my-input v-model="msg"></my-input>

// 等同于
<my-input :title="msg" @change="msg = $event"></my-input>
复制代码

使用 .sync 修饰符

Vue 提供一个 .sync 的修饰符,效果跟 v-model 同样,也是便于子组件数据更改后自动更新父组件相关数据。实现 .sync 的方式与实现 v-model 殊途同归,区别就是抛出的事件名须要是 update:myPropName 的结构。

仍是拿上面的 MyInput 说明,咱们仍是传入一个 title 的 prop,同时组件内部抛出 update:title 事件,代码以下:

// MyInput 组件中,修改抛出的事件名为 update:title
 <input type="text" :value="title" @input="$emit('update:title', $event.target.value)" />
复制代码

此时若是使用这个组件,正常应该是这样:

<my-input :title="msg" @update:title="msg = $event"></my-input>
复制代码

但此时能够使用 .sync 修饰符来简化:

<my-input :title.sync="msg"></my-input>
复制代码

能够看到 .syncv-model 所能达到的效果是同样的,用什么就看你什么场景,通常表单组件上都是用 v-model

Vue3 中的 v-model

上面说了那么多,为的就是接下来区别出 Vue3 中 v-model 带来的变化,主要变化有如下几处:

修改默认 prop 名和事件名

当用在自定义组件上时,v-model 默认绑定的 prop 名从 value 变为 modelValue,而事件名也从默认的input 改成 update:modelValue 。在 Vue3 中编写上面那个 MyInput 组件时,就须要这样:

<!-- MyInput 组件代码 Vue3 版 -->

<template>
  <div>
    <input type="text" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" // 事件名改成 update:modelValue />
  </div>
</template>

<script> export default { props: { modelValue: String, // 默认 prop 从 value 改成 modelValue }, }; </script>
复制代码

使用组件时:

<my-input v-model="msg"></my-input>

// 等同于
<my-input :modelValue="msg" @update:modelValue="msg = $event"></my-input>
复制代码

废除 model 选项和 .sync 修饰符

Vue3 中移除了 model 选项,这样就不能够在组件内修改默认 prop 名了。如今有一种更简单的方式,就是直接在 v-model 后面传递要修改的 prop 名:

// 要修改默认 prop 名,只需在 v-model 后面接上 :propName,例如修改成 title
<my-input v-model:title="msg"></my-input>

// 等同于
<my-input :title="msg" @update:title="msg = $event"></my-input>
复制代码

注意组件内部也要修改 props:

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

<script> export default { // 此时这里不须要 model 选项来修改了 props: { title: String, // 修改成 title,注意 template 中也要修改 }, }; </script>
复制代码

同时,.sync 修饰符也被移除了,若是你尝试使用它,会报这样的错误:

'.sync' modifier on 'v-bind' directive is deprecated. Use 'v-model:propName' instead

错误提示中说明了,能够使用 v-model:propName 的方式来替代 .sync,由于本质上效果是同样的。

使用多个 v-model

Vue3 中支持使用多个 v-model,属于新增功能,我很喜欢这个功能,使得组件数据更新更灵活。例若有这样一个表单子组件,用户输入的多个数据都须要更新到父组件中显示,能够这样写:

<!-- 表单子组件 Form -->

<template>
  <div class="form">
    
    <label for="name">姓名</label>
    <input id="name" type="text" :value="name" @input="$emit('update:name',$event.target.value)">
    
    <label for="address">地址</label>
    <input id="address" type="text" :value="address" @input="$emit('update:address',$event.target.value)">
  
  </div>
</template>

<script> export default { props:{ name: String, address: String } } </script>
复制代码

父组件使用这个组件时:

<child-component v-model:name="name" v-model:address="address"></child-component>
    
// 将用户输入数据更新到父组件中显示
<p>{{name}}</p>
<p>{{address}}</p>
复制代码

自定义 v-model 修饰符

在 Vue2 中的 v-model 上,咱们用过 .trim.lazy.number这三个内置修饰符,而 Vue3 则在这个基础上增长了自定义修饰符,即开发者能够自定义修饰符,以按需处理绑定值。

当咱们在 v-model 后面加上自定义修饰符后,会经过名为 modelModifiers 的 prop 传递给子组件,子组件拿到这个修饰符名后,根据条件修改绑定值。咱们来看一个例子,自定义一个修饰符 capitalize,用于将输入字符串的首字母大写。

假设自定义组件仍是叫 MyInput,使用 v-model 时加上自定义修饰符 capitalize

<my-input v-model.capitalize="msg"></my-input>
复制代码

因为不是内置修饰符,因此须要咱们本身在组件内部处理修饰符逻辑,编写组件:

<!-- MyInput 组件 -->

<template>
  <div>
    <input type="text" :value="modelValue" @input="emitValue" />
  </div>
</template>

<script> export default { props: { modelValue: String, modelModifiers: { // 自定义修饰符会默认传入这个 prop 中 type: Object, default: () => ({}), }, }, mounted() { // 当组件 v-model 后面加上了自定义修饰符,组件内部会在 modelModifiers 上获取到修饰符状态 console.log(this.modelModifiers); // {capitalize: true} }, methods: { emitValue(e) { let value = e.target.value; // 若是使用了自定义修饰符,即状态为 true,就处理值 if (this.modelModifiers.capitalize) { value = value.charAt(0).toUpperCase() + value.slice(1); } // emit value this.$emit("update:modelValue", value); }, }, }; </script>
复制代码

这样就完成了一个将输入字符串首字母大写的v-model修饰符。

若是是 v-model 带上了参数,同时使用了自定义修饰符,好比这样:

<my-input v-model:title.capitalize="msg"></my-input>
复制代码

那么传入组件内部的 prop 就再也不是 modelModifiers 了,而是 titleModifiers。它的格式是 arg + 'Modifiers'。此时这个组件应该这样写:

<!-- MyInput 组件 -->

<template>
  <div>
    <input type="text" :value="title" @input="emitValue" />
  </div>
</template>

<script> export default { props: { title: String, // modelValue -> title titleModifiers: { // modelModifiers -> titleModifiers type: Object, default: () => ({}), }, }, mounted() { console.log(this.titleModifiers); // {capitalize: true} }, methods: { emitValue(e) { let value = e.target.value; // 若是使用了自定义修饰符,就处理值 if (this.titleModifiers.capitalize) { value = value.charAt(0).toUpperCase() + value.slice(1); } // emit value this.$emit("update:title", value); }, }, }; </script>
复制代码

结语

以上就是 v-model 在 Vue2 和 Vue3 中的区别表现,我我的以为新版中使用逻辑更清晰了。只是有些部分再也不兼容了,若是是直接将 Vue2 版本代码移植到 Vue3 项目中,须要注意 v-model 的实现差别,还有注意要将 .sync 修饰符替换成新写法。

文中若有不当之处,欢迎评论区指出。