二十四、v-model的本质


v-model的用法,总结起来就是两个场景:

  1. 表单元素和响应式数据双向绑定
  2. 父子组件传递数据

和表单元素绑定

<template>
  <div>
    <p>输入的内容为:{{ message }}</p>
    <input type="text" v-model="message" placeholder="请输入内容" />
  </div>
</template>

<script setup>
import { ref } from 'vue'
const message = ref('Hello')
</script>

<style>
input {
  padding: 8px;
  margin-top: 10px;
  border: 1px solid #ccc;
  border-radius: 4px;
}
</style>

在上面的示例中,input 元素和 message 这个响应式数据做了双向绑定。

input 元素所输入的值会影响 message 这个响应式数据的值;message 响应式数据的改变也会影响 input 元素。

和子组件进行绑定

App.vue

<template>
  <div class="app-container">
    <h1>请给产品打分:</h1>
    <!-- 通过 v-model 将父组件的状态值传递给子组件 -->
    <RatingComponent v-model="rating"/>
    <p v-if="rating > 0">您的评分:{{ rating }}/5</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import RatingComponent from '@/components/RatingComponent.vue'
const rating = ref(3) // 评分的状态值
</script>

<style>
.app-container {
  max-width: 600px;
  margin: auto;
  text-align: center;
  font-family: Arial, sans-serif;
}

p {
  font-size: 18px;
  color: #333;
}
</style>

RatingComponent.vue

<template>
  <div class="rating-container">
    <span v-for="star in 5" :key="star" class="star" @click="setRating(star)">
      {{ model >= star ? '★' : '☆' }}
    </span>
  </div>
</template>

<script setup>
// 接收父组件通过 v-model 传递过来的状态
const model = defineModel()

function setRating(newRating) {
  // 通过 $emit 方法将新的评分值传递给父组件
  // emit('update:modelValue', newRating);
  model.value = newRating
}
</script>

<style scoped>
.rating-container {
  display: flex;
  font-size: 24px;
  cursor: pointer;
}

.star {
  margin-right: 5px;
  color: gold;
}

.star:hover {
  color: orange;
}
</style>

父组件通过 v-model 将自身的数据传递给子组件,子组件通过 defineModel 来拿到父组件传递过来的数据。拿到这个数据之后,不仅可以使用这个数据,还可以修改这个数据。

v-model 的本质

通过 vite-plugin-inspect 插件的编译结果来进行分析验证。

首先我们分析第一个场景,和表单元素的双向绑定,编译结果如下:


从编译结果我们可以看出,v-model 会被展开为一个名为 onUpdate:modelValue 的自定义事件,该事件对应的事件处理函数:

$event => ($setup.message) = $event;

这就解释了为什么输入框输入的值的时候,会影响响应式数据。

而输入框的 value 本身又是和 $setup.message 绑定在一起的,$setup.message 一变化,就会导致渲染函数重新执行,从而看到输入框里面的内容发生了变化。

接下来分析第二个场景,在子组件上面使用 v-model,编译结果如下:

App.vue


这里会向子组件传递一个名为 modelValue 的 props,props 对应的值就是 $setup.rating,这正是父组件上面的状态。

除此之外向子组件也传递了一个名为 onUpdate:modelValue 的自定义事件,该事件所对应的事件处理函数:

// 该事件处理函数负责的事情:
// 就是将接收到的值更新组件本身的数据 rating
$event => ($setup.rating) = $event;

RatingComponent.vue


对于子组件来讲,就可以通过 modelValue 这个props 来拿到父组件传递过来的数据,并且可以在模板中使用该数据。

当更新数据的时候,就去触发父组件传递过来的 onUpdate:modelValue 自定义事件,并且将新的值传递过去。

至此,你对官网的这句话:

defineModel 是一个便利宏。编译器将其展开为以下内容:

  • 一个名为 modelValue 的 prop,本地 ref 的值与其同步;
  • 一个名为 update:modelValue 的事件,当本地 ref 的值发生变更时触发。

有些时候在子组件上面使用 v-model 的时候,可以使用具名的 v-model,此时展开的 props 和自定义事件的名称会有所不同。

  • Props:modelValue —> title
  • 自定义事件:update:modelValue —> update:title

文章作者: 吴俊杰
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 吴俊杰 !
  目录