对 v-model 的理解
上次嘉为面试的时候面试官让我谈谈 v-model
的原理,我的回答是利用 Object.defineproperty()
这个 API 对数据进行劫持监听,并通过其中的 getter
和 setter
方法对数据进行更新(这个其实是 Vue2 响应式的原理,面试官更希望让我说说剥开v-model
这层语法糖其中具体的封装细节,之前在仿制 UI 组件库的时候有接触到这点,但当时只是简单地跟着视频封装实现而已,后来并面有进行归纳总结,时间一久就忘记了)。
v-model 的本质
首先 v-model
是 Vue 中实现数据双向绑定的一个指令,本质上是一个语法糖,它将视图组件(如 input
、textarea
、select
等)的值和组件内部定义的值进行双向绑定,当视图组件的值发生变化时,相应的组件内部的值也会随之改变,并且当组件内部的值发生变化时,相应的视图组件的值也会随之改变。具体来说背后涉及到两个操作:首先是 v-bind 动态绑定 value 属性值,然后由 v-on 绑定 input 事件监听到函数中,函数获取最新值并赋值给绑定的属性中。 语法糖和原生代码写法区别具体如下:
<div>
{{ name }}<br />
<!--v-model语法糖写法-->
<input v-model="name" />
<!--原生写法-->
<input v-bind:value="name" v-on:input="name = $event.target.value" />
</div>
使用 v-model
就相当于绑定 value
并触发 input
事件,上述代码中两种写法都能实现相同的页面效果:
v-model 修饰符
.lazy 修饰符
.lazy 修饰符用于将组件的更新延迟到 change 事件或者失去焦点事件之后,而不是在每次输入事件中更新,默认情况下是在 input 事件中更新。
.number 修饰符
.number 修饰符用于将输入的字符串转换为数字类型,在数据绑定过程中自动进行。
.trim 修饰符
.trim 修饰符用于去除输入字符串的首位空白字符,可以用于处理用户输入的名称、邮箱等等。
.self 修饰符
.self 修饰符可以用于忽略模板中特定元素的 v-model 事件,只有在事件的目标元素是当前元素本身时,才会触发绑定。它可以用于阻止事件冒泡,只在当前元素上触发 v-model 绑定的数据变化。
v-model 的副作用
如果 v-model 绑定的目标是响应式对象中不存在的一个属性,Vue 会动态将这个不存在的属性添加到响应式对象当中,并使其具备响应式。
<div>
<input v-model="author.age" />
</div>
export default {
data() {
return {
author: {
name: 'PandaGuo'
}
}
}
}
可以看到在 author
对象中会新增一个响应式属性 age
,如果不注意这个问题,可能会导致代码运行效率变慢,因为每个动态新添加的属性都需要 Vue 来进行处理,会占用额外内存,同时开发过程很容易忽视这个动态添加的属性从而增加代码的调试难度。
实现自定义组件支持 v-model
一般情况下自己自定义封装的组件想要实现支持 v-model 实现数据双向绑定,可以在自定义组件当中提供一个 model 属性。model 属性本身具备默认值:
export default {
model: {
prop: 'value',
event: 'input'
}
}
如果在自定义组件中没有定义 model
属性或者直接按照上述代码定义 model
属性,那么在外部使用该自定义组件时, v-model
就等价于 :value = " name "
加上 @input = " name = $event "
。
所以我们还可以利用 model
属性去自定义 prop
和 event
事件名称,具体实现代码如下:
在自定义组件 Custom.vue 中:
<template>
<div>
I love {{ food }}
</div>
</template>
<script>
export default {
name: "Custom.vue",
props: {
food: {
type: String,
default: 'KFC'
}
},
model: { // 通过 model 自定义 prop 和 event
prop: 'food',
event: 'bestFood'
},
}
</script>
在父组件中引用自定义组件:
<template>
<div>
<!--v-model写法-->
<custom v-model="foodList[index]"></custom>
<!--原生写法-->
<custom :food="foodList[index]" @bestFood="food = $event"></custom>
<button @click="index = 0">Hot pot</button>
<button @click="index = 1">BBQ</button>
</div>
</template>
<script>
import custom from "@/components/Custom.vue";
export default {
components: {
custom
},
data() {
return {
foodList: ['Hot pot', 'BBQ'],
index: 0
}
}
}
</script>
最终实现效果如下:
参考资料:
全网最详细的v-model讲解 - 掘金 (juejin.cn)