Vue 3 v-model 语法糖:从编译到运行,深度解剖!
大家好,各位靓仔靓女们!今天咱们来聊聊 Vue 3 中一个看似简单,实则暗藏玄机的家伙——v-model。 别看它只有短短几个字符,却能玩转各种表单元素,实现数据双向绑定,简直是个小精灵!
今天我们就来扒一扒它的底裤,看看它在编译时和运行时都做了些什么,以及在不同元素类型上又有什么骚操作。 准备好了吗? Let’s go!
一、v-model:语法糖的甜蜜外衣
v-model,顾名思义,就是 Vue 为了简化数据双向绑定而提供的一种语法糖。 所谓语法糖,就是一种让代码更简洁易读的语法形式,但实际上最终会被编译器转换成更底层的代码。
举个栗子:
<template>
<input type="text" v-model="message">
<p>Message: {{ message }}</p>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const message = ref('');
return {
message
};
}
};
</script>
这段代码看起来很简单,v-model="message" 就像魔法一样,把 input 框的值和 message 这个响应式变量绑定在一起了。 但实际上,Vue 在背后做了很多工作。
二、编译时转换:揭秘 v-model 的真面目
Vue 的编译器会将 v-model 转换成更底层的代码。 那么,到底会转换成什么呢?
对于上面的例子,v-model="message" 会被转换成如下形式:
<template>
<input
type="text"
:value="message"
@input="$event => message = $event.target.value"
>
<p>Message: {{ message }}</p>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const message = ref('');
return {
message
};
}
};
</script>
看到了吗? 原来 v-model 实际上是 v-bind (简写为 :) 和 v-on (简写为 @) 的结合体!
:value="message": 将message响应式变量的值绑定到 input 框的value属性上,实现了数据到视图的单向绑定。@input="$event => message = $event.target.value": 监听 input 框的input事件,当输入框的值发生变化时,将新的值赋给message响应式变量,实现了视图到数据的单向绑定。
这样一来,就完成了数据的双向绑定!
总结一下:
| 语法糖 | 等价形式 |
|---|---|
v-model="value" |
:value="value" @input="$event => value = $event.target.value" |
三、运行时实现:响应式系统的幕后推手
编译时转换只是第一步,真正让 v-model 运转起来的是 Vue 的响应式系统。
当我们修改 input 框的值时,会触发 input 事件,然后执行 @input 绑定的事件处理函数。 这个事件处理函数会修改 message 响应式变量的值。
由于 message 是一个响应式变量 (通过 ref 创建),当它的值发生变化时,Vue 的响应式系统会通知所有依赖于它的视图进行更新。 于是,<p>Message: {{ message }}</p> 也会自动更新,显示最新的值。
这就是 v-model 的核心原理:通过编译时转换和运行时响应式系统的配合,实现了数据的双向绑定。
四、v-model 在不同元素类型上的差异
虽然 v-model 的基本原理相同,但在不同元素类型上,它的行为可能会有所不同。 这主要是因为不同元素触发的事件以及需要绑定的属性不同。
1. <input type="text"> 和 <textarea>
这是最常见的用法,我们前面已经介绍过了。 绑定 value 属性,监听 input 事件。
2. <input type="checkbox">
对于 checkbox,v-model 绑定的是 checked 属性,监听的是 change 事件。
<template>
<input type="checkbox" v-model="checked">
<p>Checked: {{ checked }}</p>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const checked = ref(false);
return {
checked
};
}
};
</script>
会被编译成:
<template>
<input
type="checkbox"
:checked="checked"
@change="$event => checked = $event.target.checked"
>
<p>Checked: {{ checked }}</p>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const checked = ref(false);
return {
checked
};
}
};
</script>
3. <input type="radio">
Radio 也是绑定 checked 属性,监听 change 事件,但需要注意,多个 radio 应该共享同一个 v-model 的值。
<template>
<input type="radio" id="one" value="one" v-model="picked">
<label for="one">One</label>
<input type="radio" id="two" value="two" v-model="picked">
<label for="two">Two</label>
<p>Picked: {{ picked }}</p>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const picked = ref('');
return {
picked
};
}
};
</script>
4. <select>
对于 select 元素,v-model 绑定的是 value 属性,监听的是 change 事件。
<template>
<select v-model="selected">
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
</select>
<p>Selected: {{ selected }}</p>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const selected = ref('A');
return {
selected
};
}
};
</script>
会被编译成:
<template>
<select
:value="selected"
@change="$event => selected = $event.target.value"
>
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
</select>
<p>Selected: {{ selected }}</p>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const selected = ref('A');
return {
selected
};
}
};
</script>
总结一下:
| 元素类型 | 绑定的属性 | 监听的事件 |
|---|---|---|
<input type="text"> |
value |
input |
<textarea> |
value |
input |
<input type="checkbox"> |
checked |
change |
<input type="radio"> |
checked |
change |
<select> |
value |
change |
五、自定义组件上的 v-model
v-model 不仅可以用于原生 HTML 元素,还可以用于自定义组件。 这使得我们可以创建更加灵活和可复用的组件。
在自定义组件上使用 v-model 时,我们需要做一些额外的配置。
1. 定义 modelValue prop
自定义组件需要接收一个名为 modelValue 的 prop,用于接收父组件传递过来的值。
2. 触发 update:modelValue 事件
当组件内部的值发生变化时,需要触发一个名为 update:modelValue 的事件,并将新的值作为事件参数传递给父组件。
举个栗子:
<!-- MyInput.vue -->
<template>
<input
type="text"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
>
</template>
<script>
export default {
props: {
modelValue: {
type: String,
default: ''
}
},
emits: ['update:modelValue']
};
</script>
<!-- ParentComponent.vue -->
<template>
<MyInput v-model="message"></MyInput>
<p>Message: {{ message }}</p>
</template>
<script>
import { ref } from 'vue';
import MyInput from './MyInput.vue';
export default {
components: {
MyInput
},
setup() {
const message = ref('');
return {
message
};
}
};
</script>
在这个例子中,MyInput 组件接收 modelValue prop,并触发 update:modelValue 事件。 父组件使用 v-model="message" 将 message 响应式变量和 MyInput 组件绑定在一起。
v-model="message" 会被编译器转换成:
<MyInput
:modelValue="message"
@update:modelValue="newValue => message = newValue"
></MyInput>
3. v-model 参数
从 Vue 3.6 开始,v-model 支持参数,允许组件支持多个 v-model 绑定,每个绑定使用不同的 prop 和事件。
例如,你可以这样使用:
<MyComponent v-model:title="pageTitle" v-model:content="pageContent" />
在 MyComponent 内部,你需要定义 title 和 content 对应的 prop 和事件:
<!-- MyComponent.vue -->
<template>
<input type="text" :value="title" @input="$emit('update:title', $event.target.value)">
<textarea :value="content" @input="$emit('update:content', $event.target.value)"></textarea>
</template>
<script>
export default {
props: {
title: String,
content: String,
},
emits: ['update:title', 'update:content'],
};
</script>
这样,v-model:title 就会绑定 title prop 和 update:title 事件,v-model:content 就会绑定 content prop 和 update:content 事件。
六、总结
v-model 是 Vue 中一个非常方便的语法糖,它简化了数据双向绑定的操作。 通过了解它的编译时转换和运行时实现,我们可以更好地理解 Vue 的工作原理,并能够更加灵活地使用它。
- 编译时转换:
v-model实际上是v-bind和v-on的结合体。 - 运行时实现: 依赖于 Vue 的响应式系统。
- 不同元素类型:
v-model在不同元素类型上的行为略有不同,主要是因为不同元素触发的事件和需要绑定的属性不同。 - 自定义组件: 自定义组件可以通过定义
modelValueprop 和触发update:modelValue事件来实现v-model的支持。 v-model参数: 可以实现组件支持多个v-model绑定。
希望今天的分享对大家有所帮助! 记住,深入理解原理才能更好地运用技术。 下次再见!