Skip to content

v-model 是 Vue 中最常用且最优雅的双向绑定指令。在 Vue 3 中,v-model 的实现更加灵活和强大,其核心原理是编译时转换。它并非魔法,而是编译器将 v-model 语法糖自动转换为 props 和事件的组合。本节将深入解析 v-model 的编译原理,揭示它如何被转换为 modelValue + onUpdate:modelValue 的标准通信模式。


一、v-model 的本质:语法糖

v-model 是一个指令(Directive),它在编译时被转换为:

  1. 一个 prop(默认为 modelValue)。
  2. 一个事件监听器(默认为 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-bindv-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 是一个组合式函数,它封装了 propsemit 的逻辑,使代码更简洁。


七、自定义 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 的编译原理可以概括为:

  1. 语法糖v-model 不是内置功能,而是编译器提供的便捷写法。
  2. 标准通信:被转换为 prop(如 modelValue)和事件(如 update:modelValue)的组合。
  3. 灵活扩展
    • 支持自定义参数:v-model:xxx
    • 支持多个绑定。
    • 支持修饰符。
  4. 子组件配合:子组件需声明 prop 并通过 $emit 触发更新事件。
  5. 现代语法useModeldefineModel 进一步简化了实现。

理解 v-model 的编译原理,有助于开发者:

  • 正确实现可复用的表单组件。
  • 调试 v-model 相关问题。
  • 充分利用 Vue 3 的新特性提升开发效率。

v-model 的设计完美体现了 Vue 的核心思想:以简洁的 API 封装复杂的逻辑,让开发者专注于业务,而非通信细节