深入理解 Vue 3 中 `v-model` 语法糖的编译时转换和运行时实现,包括其在不同元素类型上的差异。

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 内部,你需要定义 titlecontent 对应的 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-bindv-on 的结合体。
  • 运行时实现: 依赖于 Vue 的响应式系统。
  • 不同元素类型: v-model 在不同元素类型上的行为略有不同,主要是因为不同元素触发的事件和需要绑定的属性不同。
  • 自定义组件: 自定义组件可以通过定义 modelValue prop 和触发 update:modelValue 事件来实现 v-model 的支持。
  • v-model参数: 可以实现组件支持多个 v-model 绑定。

希望今天的分享对大家有所帮助! 记住,深入理解原理才能更好地运用技术。 下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注