v-model
是 Vue 中使用频率特别高的一个指令,而 Vue3 中的 v-model
有了很大的变化,本文将详细讲述一下 Vue2 和 Vue3 中的 v-model
的区别。javascript
若是对 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>
复制代码
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>
复制代码
能够看到 .sync
和 v-model
所能达到的效果是同样的,用什么就看你什么场景,通常表单组件上都是用 v-model
。
上面说了那么多,为的就是接下来区别出 Vue3 中 v-model
带来的变化,主要变化有如下几处:
当用在自定义组件上时,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>
复制代码
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
,由于本质上效果是同样的。
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>
复制代码
在 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
修饰符替换成新写法。
文中若有不当之处,欢迎评论区指出。