v-model 是 Vue 中最常用且最优雅的双向绑定指令。在 Vue 3 中,v-model 的实现更加灵活和强大,其核心原理是编译时转换。它并非魔法,而是编译器将 v-model 语法糖自动转换为 props 和事件的组合。本节将深入解析 v-model 的编译原理,揭示它如何被转换为 modelValue + onUpdate:modelValue 的标准通信模式。
一、v-model 的本质:语法糖
v-model 是一个指令(Directive),它在编译时被转换为:
- 一个
prop(默认为modelValue)。 - 一个事件监听器(默认为
update:modelValue)。
这种设计遵循了 Vue 组件“单向数据流”的原则:父组件通过 prop 向子组件传递数据,子组件通过事件通知父组件数据变化。
二、基础转换:默认 v-model
1. 模板写法
vue
<Child v-model="searchText" />2. 编译后的 render 函数
js
function render() {
return h(Child, {
modelValue: this.searchText,
'onUpdate:modelValue': value => this.searchText = value
});
}3. 等效的模板写法
vue
<Child
:modelValue="searchText"
@update:modelValue="searchText = $event"
/>4. 转换规则
- Prop 名称:
modelValue(默认)。 - 事件名称:
update:modelValue。 - 赋值逻辑:事件回调中执行
this.searchText = value。
三、自定义 prop 和事件:v-model 参数
Vue 3 允许通过 v-model 的参数(Argument)自定义 prop 和事件名称。
1. 模板写法
vue
<Child v-model:query="searchText" />2. 编译后的 render 函数
js
function render() {
return h(Child, {
query: this.searchText,
'onUpdate:query': value => this.searchText = value
});
}3. 等效模板
vue
<Child
:query="searchText"
@update:query="searchText = $event"
/>4. 转换规则
- Prop 名称:
query(由v-model:query的参数决定)。 - 事件名称:
update:query。 - 通用公式:
v-model:[arg]="value"→:[arg]="value"+@update:[arg]="$event => value = $event"。
四、多 v-model 绑定
一个组件可以同时接收多个 v-model,这在表单控件中非常有用。
1. 模板写法
vue
<Child
v-model:username="user.name"
v-model:password="user.pass"
/>2. 编译后的 render 函数
js
function render() {
return h(Child, {
username: this.user.name,
'onUpdate:username': value => this.user.name = value,
password: this.user.pass,
'onUpdate:password': value => this.user.pass = value
});
}3. 优势
- 清晰地表达了组件需要同步多个状态。
- 避免了使用多个
v-bind和v-on的冗长写法。
五、v-model 修饰符(Modifiers)
v-model 支持修饰符,如 .trim、.number、.lazy,这些修饰符也会被编译器处理。
1. 模板写法
vue
<input v-model.trim="searchText" />2. 编译后的 render 函数
js
function render() {
return h('input', {
modelValue: this.searchText,
'onUpdate:modelValue': $event => this.searchText = $event.trim()
});
}.trim:在赋值前调用trim()。.number:调用parseFloat()或parseInt()。.lazy:将事件从input改为change。
六、子组件的实现:如何配合 v-model
为了让 v-model 正常工作,子组件需要正确声明 prop 并触发事件。
1. 选项式 API
js
// Child.vue
export default {
props: ['modelValue'], // 接收 modelValue
emits: ['update:modelValue'], // 声明事件
methods: {
handleChange(event) {
// 触发 update 事件,通知父组件
this.$emit('update:modelValue', event.target.value);
}
}
}vue
<template>
<input :value="modelValue" @input="handleChange" />
</template>2. 组合式 API (setup)
vue
<script setup>
defineProps(['modelValue']);
const emit = defineEmits(['update:modelValue']);
function handleChange(event) {
emit('update:modelValue', event.target.value);
}
</script>3. 使用 useModel 的语法糖(Vue 3.4+)
vue
<script setup>
// 自动创建 modelValue prop 和 update 事件
const modelValue = useModel('modelValue');
function handleChange(event) {
modelValue.value = event.target.value; // 直接赋值
}
</script>useModel 是一个组合式函数,它封装了 props 和 emit 的逻辑,使代码更简洁。
七、自定义 v-model 配置
可以通过 defineModel(实验性)或 model 选项自定义默认的 prop 和事件名。
1. 选项式 API
js
export default {
model: {
prop: 'value',
event: 'change'
},
props: ['value']
}vue
<Child v-model="text" />
<!-- 等效于 -->
<Child :value="text" @change="text = $event" />2. 组合式 API (defineModel)
vue
<script setup>
// 自动处理 v-model,支持自定义参数
const modelValue = defineModel();
// 或带参数
const query = defineModel('query');
</script>defineModel 是 Vue 3.4+ 引入的宏,极大简化了 v-model 的实现。
八、总结
v-model 的编译原理可以概括为:
- 语法糖:
v-model不是内置功能,而是编译器提供的便捷写法。 - 标准通信:被转换为
prop(如modelValue)和事件(如update:modelValue)的组合。 - 灵活扩展:
- 支持自定义参数:
v-model:xxx。 - 支持多个绑定。
- 支持修饰符。
- 支持自定义参数:
- 子组件配合:子组件需声明
prop并通过$emit触发更新事件。 - 现代语法:
useModel和defineModel进一步简化了实现。
理解 v-model 的编译原理,有助于开发者:
- 正确实现可复用的表单组件。
- 调试
v-model相关问题。 - 充分利用 Vue 3 的新特性提升开发效率。
v-model 的设计完美体现了 Vue 的核心思想:以简洁的 API 封装复杂的逻辑,让开发者专注于业务,而非通信细节。