v-model主要是用在表单 <input>、<textarea> 、<select> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model 本质上不过是语法糖。它负责监听用户的输入事件以更新数据。
v-model 会忽略所有表单元素的 value、checked、selected attribute 的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。
v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
- text 和 textarea 元素使用 value property 和 input 事件
- checkbox 和 radio 元素使用 checked property 和 change 事件
- select 元素使用 value property 和 change 事件
注意:v-model 不会在输入法组合文字过程中得到更新。如果你也想处理这个过程,请使用 input 事件
v-model在vue2和vue3的区别
相同点
- 用在表单上都是给input绑定一个 value 值和 input 事件(默认情况)
不同点(主要用在组件上)
用在组件上(vue2)
- v-model 只能使用一次
- 默认传递 vulue属性,接受 input 事件
- 默认传递的属性和事件可通过 model 选项进行修改
用在组件上(vue3)
- v-model 只能使用一次,但可以在v-model后添加参数可使用多次
- 默认传递 modelValue 属性,接受 update:modelValue 事件,当在v-model后添加参数时如v-model:myPropName,则传递为 myPropName 属性,接受 update:myPropName事件
- 默认传递的属性和事件无法修改,必须是modelValue 和 update:modelValue
vue2中 .sync 与 v-model的区别
相同点
- 都是语法糖,都可以实现父子组件数据的双向绑定
不同点
- v-model 背后是 value 属性 和 input 事件
- myPropName.sync 背后是 myPropName 属性 和 update:myPropName 事件
- 在组件上 v-model 只可使用一次,.sync可以有多个
注意:vue3中去除了 .sync修饰符,但把相应功能合并到了 v-model 上,所以在 vue3 中,v-model 可以使用多次
v-model 简单解析
<input type="text" v-model="searchText">
等同于
<input
:value="searchText"
@input="searchText = $event.target.value"
/>
v-model用在<input type=‘radio’ />上
<label for="man">男</label>
<input type="radio" id="man" value="Man" v-model="sex" />
<label for="woman">女</label>
<input type="radio" id="woman" value="Woman" v-model="sex" />
<!-- 等同于上面 -->
<label for="man">男</label>
<input
type="radio"
id="man"
value="Man"
:checked="sex === 'Man'"
@change="handleSexChange"
/>
<label for="woman">女</label>
<input
type="radio"
id="woman"
value="Woman"
:checked="sex === 'Woman'"
@change="handleSexChange"
/>
handleSexChange (event) {
this.sex = event.target.value
}
v-model在 vue2 和 vue3 组件上的具体区别
v-model用在 vue2 组件
<!-- Test.vue -->
<CustomInput v-model="searchText" />
等同于
<CustomInput
:value="searchText"
@input="newValue => searchText = newValue"
/>
<!-- CustomInput.vue -->
<template>
<input type="text" :value="value" @input="handleInput($event)" />
</template>
<script>
export default {
props: {
value: String
},
methods: {
handleInput (event) {
this.$emit('input', event.target.value)
}
}
}
</script>
- 在 CustomInput.vue中 props内必须定义value属性,否则input元素无法使用
- 在 CustomInput.vue中,需要向外派发 input 事件去更新 value 值
如果我们想在 CustomInput.vue 中使用v-model,则需要定义同时具有 getter 和 setter 的计算属性。get 方法需返回 value prop,而 set 方法需触发相应的事件
<!-- Test.vue -->
<CustomInput v-model="searchText"></CustomInput>
<!-- CustomInput.vue -->
<template>
<input type="text" v-model="customValue" />
</template>
<script>
export default {
props: {
value: String
},
computed: {
customValue: {
get () {
return this.value
},
set (value) {
this.$emit('input', value)
}
}
}
}
</script>
model的使用
如果在 CustomInput.vue 中我们不想使用默认 props 里的 value 名和 触发 input事件,那我们可以使用 model 选项,它可以通过 prop 自定义 value 字段名并通过 event 自定义触发的 事件名
<!-- Test.vue -->
<CustomInput v-model="searchText"></CustomInput>
<!-- CustomInput.vue -->
<template>
<div>
<input type="text" :value="customValue" @input="handleInput($event)" />
</div>
</template>
<script>
export default {
model: {
prop: 'customValue',
event: 'customInput'
},
props: {
customValue: String
},
methods: {
handleInput (event) {
this.$emit('customInput', event.target.value)
}
}
}
</script>
注意:虽然我们可以在 model 中自定义任何名字,但像上面的这种写法并没有什么语义,model的出现是为了解决像单选框、复选框等类型的输入控件可能会将 value 属性用于不同的目的,使用model选项是为了避免这样的冲突,让代码更有语义
checkBox使用示例
<!-- Test.vue -->
<CustomInput v-model="isShow"></CustomInput>
<!-- CustomInput.vue -->
<template>
<input type="checkbox" :checked="checked" @change="handleChange($event)" />
</template>
<script>
export default {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
methods: {
handleChange (event) {
this.$emit('change', event.target.checked)
}
}
}
</script>
这里 isShow 的值将会传入这个名为 checked 的prop。同时当 <CustomInput/> 触发一个 change 事件并附带一个新值时,这个 isShow 也将会被更新
注意:
- 你仍然需要在组件的 props 选项里声明 checked 这个 prop
- 不管属性和事件是什么,v-model都需要绑定固定的属性和事件,这也就导致了组件上只能使用一次v-model,但并不是所有元素都有 input、change事件,而且如果我们想对多个 props 中的值进行 双向绑定 又该怎么做呢?
.sync 修饰符
sync 也是语法糖
<text-document v-bind:title.sync="doc.title"></text-document>
等同于
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
></text-document>
从代码可以看出,它和v-model的实现原理几乎一样,只是属性和事件名和v-model不同罢了。
注意:在 sync 中我们需要使用 update:myPropName 的模式触发事件,因 myPropName 属性名称不是固定的,所以事件也就不是固定的,因此 .sync 修饰符可以在组件上多次使用,此时也就支持了对多个 props 属性的 双向绑定
<!-- Test.vue -->
<CustomInput :firstName.sync="obj.firstName" :lastName.sync="obj.lastName"></CustomInput>
<!-- CustomInput.vue -->
<template>
<div>
<div><span>{{firstName}}</span><span @click="changeX">更改姓</span></div>
<div><span>{{lastName}}</span><span @click="changeM">更改名</span></div>
</div>
</template>
<script>
export default {
props: {
firstName: String,
lastName: String
},
methods: {
changeX () {
this.$emit('update:firstName', 'liu')
},
changeM () {
this.$emit('update:lastName', 'yz')
}
}
}
</script>
v-model用在vue3组件
默认情况下 vue2 的 v-model 用在表单上 和 用在组件上 表现形式是一样的。都是 :value 和 @input,但在vue3上 v-model的表现就不一样了 用在表单上是:
<input v-model="searchText" />
等同于
<input
:value="searchText"
@input="searchText = $event.target.value"
/>
而当使用在一个组件上时,v-model 会被展开为如下的形式:
<CustomInput
:modelValue="searchText"
@update:modelValue="newValue => searchText = newValue"
/>
用在组件上我们可以看到,传递给组件的属性变成了 modelValue ,接受的事件变成了 update:modelValue
<!-- Test.vue -->
<CustomInput v-model="searchText" />
<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
当我们在组件上使用 v-model 时,它的 modelValue 和 update:modelValue 都已固定,不可修改,它并没有像 vue2 中的 model 来修改这些变量名。那在vue3的组件上如何实现 多个props 属性的双向绑定呢?先不急,我们先看看v-model 的另一种用法
另一种在组件内实现 v-model 的方式是使用一个可写的,同时具有 getter 和 setter 的计算属性。get 方法需返回 modelValue prop,而 set 方法需触发相应的事件:
<!-- Test.vue -->
<CustomInput v-model="searchText" />
<!-- CustomInput.vue -->
<template>
<input v-model="value" />
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const value = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
</script>
让 props 上的多个属性实现双向绑定我们首先想到的是 .sync修饰符,但是很遗憾,vue3已经去除了 .sync修饰符,同时把 .sync 的功能都集成到 v-model上。默认情况下,v-model在组件上都是使用 modelValue 作为 prop,并以 update:modelValue 作为对应的事件。我们可以通过给 v-model 指定一个参数来更改这些名字:
在下面这个例子中,子组件应声明 firstName 和 lastName 两个prop,并通过触发 update:firstName 和 update:lastName 事件更新父组件值:
<!-- Test.vue -->
<MyComponent
v-model:first-name="first"
v-model:last-name="last"
/>
<!-- MyComponent.vue -->
defineProps({
firstName: String,
lastName: String
})
defineEmits(['update:firstName', 'update:lastName'])
</script>
<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>
v-model 内置修饰符
v-model 有一些内置的修饰符,例如 .trim,.number 和 .lazy
.lazy
默认情况下,v-model 会在每次 input 事件后更新数据 (拼字阶段不更新)。你可以添加 lazy 修饰符来改为在每次 change 事件后更新数据:
template
<!-- 在 "change" 事件后同步更新而不是 "input" -->
<input v-model.lazy="msg" />
.number
如果你想让用户输入自动转换为数字,你可以在 v-model 后添加 .number 修饰符来管理输入:
<input v-model.number="age" />
如果该值无法被 parseFloat() 处理,那么将返回原始值。如: “12ab” => 12, “12a34” => 12,“012” => 12,“ab” => ‘ab’ “ab12” => “ab12”
number 修饰符会在输入框有 type=“number” 时自动启用。
注意:如果值可以被 parseFloat处理则 typeof 返回的类型为 number, 否则返回 string
.trim
如果你想要默认自动去除用户输入内容中两端的空格,你可以在 v-model 后添加 .trim 修饰符:
<input v-model.trim="msg" />
v-model 自定义修饰符(vue3)
<!-- Test.vue -->
<MyComponent v-model.capitalize="myText" />
组件的 v-model 上所添加的修饰符,可以通过 modelModifiers prop 在组件内访问到。在下面的组件中,我们声明了 modelModifiers 这个 prop,它的默认值是一个空对象:
<!-- MyComponent.vue -->
<script setup>
const props = defineProps({
modelValue: String,
modelModifiers: { default: () => ({}) }
})
defineEmits(['update:modelValue'])
console.log(props.modelModifiers) // { capitalize: true }
</script>
<template>
<input
type="text"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
注意:这里组件的 modelModifiers prop 包含了 capitalize 且其值为 true,因为它在模板中的 v-model 绑定上被使用了
有了 modelModifiers 这个 prop,我们就可以在原生事件侦听函数中检查它的值,然后决定触发的自定义事件中要向父组件传递什么值。在下面的代码里,我们就是在每次 元素触发 input 事件时将值的首字母大写:
<!-- MyComponent.vue -->
<script setup>
const props = defineProps({
modelValue: String,
modelModifiers: { default: () => ({}) }
})
const emit = defineEmits(['update:modelValue'])
function emitValue(e) {
let value = e.target.value
if (props.modelModifiers.capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1)
}
emit('update:modelValue', value)
}
</script>
<template>
<input type="text" :value="modelValue" @input="emitValue" />
</template>
对于又有参数又有修饰符的 v-model 绑定,生成的 prop 名将是 arg + “Modifiers”。举例来说:
<MyComponent v-model:title.capitalize="myText">
相应的声明应该是:
const props = defineProps(['title', 'titleModifiers'])
defineEmits(['update:title'])
console.log(props.titleModifiers) // { capitalize: true }