Vue框架的一大核心特性就是数据的双向绑定,通过数据的改变,反映到视图上不同的渲染,从而实现系统的响应式。
如果你对实现数据双向绑定的原理感兴趣,可以看我之前写的有关发布订阅模式的文章。
而提到双向绑定,我们自然就要提到v-model
指令。或许你知道并能熟练的在表单元素上使用v-model
,但v-model
能做的事不止如此,所以本文就和大家一起探索v-model
的高级用法。
v-model是什么
v-model的本质是一个语法糖,看下面这个例子:
<input type="text" v-model="something"/>
// 等同于
<input type="text" v-bind:value="something" v-on:input="something=$event.target.value"/>
// 简写为
<input type="text" :value="something" @input="something=$event.target.value"/>
在官方文档中提到,v-model的使用有限制,只能用于
<input>
<select>
<textarea>
- components
前三个的使用相信大家都不陌生,在知道了v-model的原理后,我们接下来试试在自定义组件中使用v-model。
自定义组件上的v-model
现在我们来封装一个input组件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
</style>
</head>
<body>
<div id="demo">
<custom-input v-model="result"></custom-input>
<span>输出:{{result}}</span>
</div>
<script src="https://cdn.bootcss.com/vue/2.3.0/vue.js"></script>
<script>
Vue.component('CustomInput', {
template: `
<div>
<span>输入:</span>
<input
type="text"
:value="value"
@input="$emit('input', $event.target.value)"
/>
</div>
`,
props: ['value']
})
var demo = new Vue({
el: '#demo',
data: {
result: '6666'
}
})
</script>
</body>
</html>
结果:

但是对于初学者来说,这个例子可能会产生一些混淆,因为input元素本身可以使用v-model属性,而且自带input事件,所以我们来稍微改写下这个例子:
<div id="demo">
<custom-input v-model="result"></custom-input>
<span>输出:{{result}}</span>
</div>
<script>
Vue.component('CustomInput', {
template: `
<div>
<span>输入:</span>
<input
type="text"
v-model="innerValue"
@input="onInput"
/>
</div>
`,
props: ['value'],
data () {
return {
innerValue: this.value
}
},
methods: {
onInput (e) {
this.innerValue = e.target.value
this.$emit('input', this.innerValue)
}
}
})
var demo = new Vue({
el: '#demo',
data: {
result: '6666'
}
})
</script>
结果:

<custom-input :value="result" @input="result=arguments[0]"></custom-input>
所以由上我们可以知道,在父组件上使用v-model其实就是做了这两件事:
- 向子组件传递一个名为value的参数
- 监听子组件向上发送的一个名为input的事件
所以我们的子组件并不只限于使用类似input的表单元素,只要我们想,子组件可以是任何元素,例如我们可以这样:
<div id="demo">
<custom-input v-model="result"></custom-input>
<span>输出:{{result}}</span>
</div>
<script>
Vue.component('CustomInput', {
template: `
<div>
<span>内部数据:{{innerValue}}</span>
<button @click="onClick">改变</button>
</div>
`,
props: ['value'],
data () {
return {
innerValue: this.value
}
},
methods: {
onClick (e) {
this.innerValue = '数据变啦'
this.$emit('input', this.innerValue)
}
}
})
var demo = new Vue({
el: '#demo',
data: {
result: '原始数据'
}
})
</script>
结果:

v-model的高级用法
通过上面的解析,我们会发现,v-model总是会传递名为value的参数,监听名为input的事件,但是,当我们在创建一个自定义的单选框或复选框组件的时候,我们可能需要接收的是checked属性,传递onchagne事件。所以在Vue 2.2.0以上的版本,新增了自定义model。
官方手册描述:
类型:{ prop?: string, event?: string }
解释:允许一个自定义组件在使用 v-model 时定制 prop 和 event。
也就是说,我们可以在子组件里自定义v-model传入参数的名字和提交给父组件的事件类型,如此便捷,那事不宜迟我们马上来看看怎么使用。
封装一个checkbox组件
在不能使用自定义model以前,我们想要实现在checkbox上的v-model我们需要这样做:
<input type="checkbox" :checked="value" @change="value = $event.target.checked" />
在组件上:
<custom-checkbox v-model="result"></custom-checkbox>
Vue.component('custom-checkbox', {
tempalte: `
<input
type="checkbox"
@change="$emit('input', $event.target.checked)"
:checked="value"
/>
`,
props: ['value'],
})
当我们用上自定义的model后,情况就变成了这样:
<div id="demo">
<custom-checkbox v-model="result"></custom-checkbox>
<span>状态:{{result}}</span>
</div>
<script>
Vue.component('CustomCheckbox', {
template: `
<div>
单选框:
<input
type="checkbox"
@change="$emit('change', $event.target.checked)"
:checked="checked"/>
</div>
`,
model: {
prop: 'checked',
event: 'change'
},
props: ['checked']
})
var demo = new Vue({
el: '#demo',
data: {
result: true
}
})
</script>
结果:

封装一个select组件
在业务场景中,我们常常会因为嫌弃select组件原生那简陋的样式而用ul自己实现一个下拉框组件,有了自定义model我们可以将组件写的更直观、便捷。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.select {
margin-top: 10px;
width: 150px;
padding: 5px;
border: 1px solid #999;
border-radius: 9px;
}
.head {
height: 30px;
line-height: 30px;
}
ul {
padding: 0;
margin: 0;
}
li {
list-style: none;
height: 30px;
line-height: 30px;
}
li + li {
border-top: 1px solid #999;
}
</style>
</head>
<body>
<div id="demo">
<span>当前选中值:{{selected.value}} - {{selected.text}}</span>
<custom-select v-model="selected" :options="items"></custom-select>
</div>
<script src="https://cdn.bootcss.com/vue/2.3.0/vue.js"></script>
<script>
Vue.component('CustomSelect', {
template: `
<div class="select">
<div @click="show=!show" class="head">{{selectedItem.text||'请选择'}}</div>
<ul v-show="show">
<li
v-for="item in options"
:key="item.value"
@click="onSelect(item)"
>
{{item.text}}
</li>
</ul>
</div>
`,
model: {
prop: 'selected',
event: 'change'
},
props: ['options', 'selected'],
data () {
return {
selectedItem: this.selected,
show: false
}
},
methods: {
onSelect (item) {
this.selectedItem = item
this.show = !this.show
this.$emit('change', item)
}
}
})
var demo = new Vue({
el: '#demo',
data: {
items: [
{ id: 1, value: 'a', text: '选项一' },
{ id: 2, value: 'b', text: '选项二' },
{ id: 3, value: 'c', text: '选项三' }
],
selected: {}
}
})
</script>
</body>
</html>
结果:

v-model和修饰符
最后我们再来讲下v-model可用的修饰符,为我们的开发锦上添花。
.lazy
- 作用:在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 (除了上述输入法组合文字时)。你可以添加 lazy 修饰符,从而转变为使用 change 事件进行同步
- 使用:
<input v-model.lazy="msg" >
- 效果:
.number
- 作用:在默认情况下,v-model在每次input事件触发后将输入框的值与数据进行同步(除了上述输入法组合文字时)。添加lazy修饰符,从而转变为使用change事件进行同步
- 使用:
<input v-model.number="age" type="number">
.trim
- 作用:自动将用户的输入值转为数值类型
- 使用:
<input v-model.trim="msg">
.sync
这个修饰符是在Vue 2.3.0以上版本中新增的,严格来说,这个并不是v-model的修饰符,而是v-bind的修饰符。在有些情况下,我们可能需要对一个prop进行“双向绑定”,不幸的是,真正的双向绑定会带来维护上的问题,因为子组件可以修改父组件,且在父组件和子组件都没有明显的改动来源。所以Vue官方推荐以 update:myPropName
的模式触发事件取而代之。
// 子组件
this.$emit('update:title', newTitle)
// 父组件
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
>
</text-document>
而.sync修饰符的作用就是简化这一过程
<text-document v-bind:title.sync="doc.title"></text-document>
所以我们会发现,.sync修饰符让一个v-bind绑定数据,看起来可以被子组件修改,而本质上还是一个父子组件传值的过程,这个过程,你也可以使用上一节我们说过的v-model结合自定义model的方法实现:
// 子组件
model: {
prop: 'title',
event: 'update'
}
this.$emit('update', newTitle)
// 父组件
<text-document v-model="doc.title"></text-document>
两种方式,在表达上有所差异,而其中的优劣就看实际的开发的需要了。
以上就是关于v-model使用的全部内容,如果有什么错误或者解释不到位的地方,欢迎大家评论交流。