v-model 是 Vue 中使用频率特别高的一个指令,而 Vue3 中的 v-model 有了很大的变化,本文将详细讲述一下 Vue2 和 Vue3 中的 v-model 的区别。
Vue2 中的 v-model
如果对 Vue2 中的语法很熟悉,这部分可以不看。
首先来回顾一下 Vue2 中的 v-model,它主要用于表单元素和自定义组件上。v-model本质上是一个语法糖,会对用户的输入做一些特殊处理以达到更新数据,而所谓的处理其实就是给使用的元素默认绑定属性和事件。
当 v-model 使用在表单元素上时,会根据元素的不同而采用不同的处理:
- 当
<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,原理其实就是上面说到的绑定属性和事件。
我们可以尝试实现一下 v-model,来开发一个简单的输入组件,就叫 MyInput 吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
<template> <div> <input type="text" :value="value" @input="$emit('input',$event.target.value)"> </div> </template>
<script> export default { props: { value: String, } } </script>
|
上面代码就实现了组件的 v-model 功能,当在这个组件上使用 v-model 时:
1
| <my-input v-model="msg"></my-input>
|
其实就等同于:
1
| <my-input :value="msg" @input="msg = $event">
|
Vue 还提供了 model 选项,用于将属性或事件名称改为其他名称,比如上面的 MyInput 组件,我们改一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <template> <div> <input type="text" :value="title" @input="$emit('change', $event.target.value)" /> </div> </template>
<script> export default { model: { prop: "title", event: "change", }, props: { title: String, }, }; </script>
|
此时使用组件:
1 2 3 4
| <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 事件,代码如下:
1 2
| <input type="text" :value="title" @input="$emit('update:title', $event.target.value)" />
|
此时如果使用这个组件,正常应该是这样:
1
| <my-input :title="msg" @update:title="msg = $event"></my-input>
|
但此时可以使用 .sync 修饰符来简化:
1
| <my-input :title.sync="msg"></my-input>
|
可以看到 .sync 和 v-model 所能达到的效果是一样的,用什么就看你什么场景,一般表单组件上都是用 v-model。
Vue3 中的 v-model
上面说了那么多,为的就是接下来区别出 Vue3 中 v-model 带来的变化,主要变化有以下几处:
修改默认 prop 名和事件名
当用在自定义组件上时,v-model 默认绑定的 prop 名从 value 变为 modelValue,而事件名也从默认的input 改为 update:modelValue 。在 Vue3 中编写上面那个 MyInput 组件时,就需要这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
<template> <div> <input type="text" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" // 事件名改为 update:modelValue /> </div> </template>
<script> export default { props: { modelValue: String, }, }; </script>
|
使用组件时:
1 2 3 4
| <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 名:
1 2 3 4 5
| <my-input v-model:title="msg"></my-input>
<my-input :title="msg" @update:title="msg = $event"></my-input>
|
注意组件内部也要修改 props:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <div> <input type="text" :value="title" @input="$emit('update:title', $event.target.value)" /> </div> </template>
<script> export default { props: { title: String, }, }; </script>
|
同时,.sync 修饰符也被移除了,如果你尝试使用它,会报这样的错误:
‘.sync’ modifier on ‘v-bind’ directive is deprecated. Use ‘v-model:propName’ instead
错误提示中说明了,可以使用 v-model:propName 的方式来替代 .sync,因为本质上效果是一样的。
使用多个 v-model
Vue3 中支持使用多个 v-model,属于新增功能,我很喜欢这个功能,使得组件数据更新更灵活。例如有这样一个表单子组件,用户输入的多个数据都需要更新到父组件中显示,可以这样写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
<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>
|
父组件使用这个组件时:
1 2 3 4 5
| <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:
1
| <my-input v-model.capitalize="msg"></my-input>
|
由于不是内置修饰符,所以需要我们自己在组件内部处理修饰符逻辑,编写组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
<template> <div> <input type="text" :value="modelValue" @input="emitValue" /> </div> </template>
<script> export default { props: { modelValue: String, modelModifiers: { type: Object, default: () => ({}), }, }, mounted() { console.log(this.modelModifiers); }, methods: { emitValue(e) { let value = e.target.value; if (this.modelModifiers.capitalize) { value = value.charAt(0).toUpperCase() + value.slice(1); } this.$emit("update:modelValue", value); }, }, }; </script>
|
这样就完成了一个将输入字符串首字母大写的v-model修饰符。
如果是 v-model 带上了参数,同时使用了自定义修饰符,比如这样:
1
| <my-input v-model:title.capitalize="msg"></my-input>
|
那么传入组件内部的 prop 就不再是 modelModifiers 了,而是 titleModifiers。它的格式是 arg + ‘Modifiers’。此时这个组件应该这样写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
<template> <div> <input type="text" :value="title" @input="emitValue" /> </div> </template>
<script> export default { props: { title: String, titleModifiers: { type: Object, default: () => ({}), }, }, mounted() { console.log(this.titleModifiers); }, methods: { emitValue(e) { let value = e.target.value;
if (this.titleModifiers.capitalize) { value = value.charAt(0).toUpperCase() + value.slice(1); } this.$emit("update:title", value); }, }, }; </script>
|
参考文章
v-model 在 Vue2 和 Vue3 中的区别