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
在不同元素类型上的行为略有不同,主要是因为不同元素触发的事件和需要绑定的属性不同。 - 自定义组件: 自定义组件可以通过定义
modelValue
prop 和触发update:modelValue
事件来实现v-model
的支持。 v-model
参数: 可以实现组件支持多个v-model
绑定。
希望今天的分享对大家有所帮助! 记住,深入理解原理才能更好地运用技术。 下次再见!