Appearance
组件 v-model
v-model 是 Vue 中的双向数据绑定指令。它既可以配合表单元素一起使用,又能配合组件一起使用。示例代码如下:
html
<!-- 表单元素的双向数据绑定 -->
<input v-model="msg" />
<!-- 组件上的双向数据绑定 -->
<Counter v-model="count" />
defineModel() 宏函数
defineModel()
是 Vue3.4 新增的宏函数,极大的简化了实现组件上 v-model 的过程,它是实现组件上的 v-model 的核心 API。
基本用法
实现父子组件之间 count
值的双向数据绑定。
在父组件中进行 v-model 的绑定
- 在父组件中定义名为
num
的响应式数据,并实现数值的自增和自减功能; - 在父组件中导入并使用
MyCounter
子组件,并通过v-model="num"
把父组件的 num 值双向绑定给子组件;
父组件的示例代码如下:
vue
<script setup>
import MyCounter from './components2/MyCounter.vue'
import { ref } from 'vue'
const num = ref(0)
</script>
<template>
<h1>父组件中的 num 值是:{{ num }}</h1>
<button @click="num--">-</button>
<button @click="num++">+</button>
<hr />
<MyCounter v-model="num" />
</template>
在子组件中调用 defineModel() 宏函数
在子组件中调用 defineModel()
函数,并使用 count
来接收其返回值。返回值 count
是可读可写的,子组件中对 count
值的修改会自动同步到父组件的 num
中:
vue
<script setup>
// 1. 接收父组件传入的 num 数值, 并转存到 count 中
// 请注意: 这里得到的 count 是"可读可写"的
const count = defineModel()
</script>
<template>
<!-- 2. 在页面上读取 count 的值 -->
<h3>count 的值是:{{ count }}</h3>
<!-- 3. 修改 count 的值 -->
<button @click="count--">-</button>
<button @click="count++">+</button>
</template>
🚨 注意 🚨
组件的 Props 是只读的,而调用 defineModel() 得到的返回值是可读可写的。
defineModel() 的底层原理
defineModel()
简化了子组件中实现 v-model 功能的代码逻辑。在底层它用到了 Props 和 Ref 和 Emit 自定义事件。
其中:
- Props 负责接收父组件通过
v-model="num"
传入的数据,默认生成的 prop 名称是modelValue
; - Ref 负责把只读的
modelValue
prop 的值,转存到count
响应式数据中; - Emit 负责把响应式数据的变更以自定义事件的形式传出给父组件,从而更新父组件中的数据,默认生成的事件名是
update:modelValue
;
手动实现原始的 v-model 功能
在 defineModel()
宏函数出现之前,程序员需要在子组件中使用 Props、Ref 和 Emit 这种最原始的方式实现组件上的 v-model 功能。
子组件 MyCounter2
的示例代码如下:
vue
<script setup>
const props = defineProps({
num: Number
})
// 约定:用以实现组件上 v-model 功能的自定义事件,
// 其名称要以 update: 开头,并且在其后跟上要更新的 prop 的名称
defineEmits(['update:num'])
import { ref, onBeforeUpdate } from 'vue'
const count = ref(props.num)
// 如果传入的 num 值发生了变化,则更新本地 count 的值
onBeforeUpdate(() => {
count.value = props.num
})
</script>
<template>
<h3>count 的值是:{{ count }}</h3>
<!-- 注意这里的 --count 和 ++count -->
<!-- 是先对 --count 或 ++count 表达式求值,再把求值的结果作为参数传给 $emit -->
<button @click="$emit('update:num', --count)">-</button>
<button @click="$emit('update:num', ++count)">+</button>
</template>
父组件中的代码如下:
vue
<script setup>
import MyCounter2 from './components2/MyCounter2.vue'
import { ref } from 'vue'
const num = ref(0)
</script>
<template>
<h1>父组件中的 num 值是:{{ num }}</h1>
<button @click="num--">-</button>
<button @click="num++">+</button>
<hr />
<!-- 通过 prop 向下传入数据 -->
<!-- 通过监听自定义事件,把子组件传出的数据,转存到自身的响应式数据 num 中 -->
<MyCounter2 :num @update:num="(value) => (num = value)" />
</template>
其它用法
v-model 的参数
设置 prop 名称
在默认情况下,子组件中调用 defineModel()
宏函数之后,会在子组件中生成一个名为 modelValue
的 prop。如果我们想要自定义这个 prop 的名称,可以在调用 defineModel()
的时候传入第 1 个参数:
js
const count = defineModel('numb')
现在,子组件中生成的 prop 名称就变成了 numb
:

相应的,父组件需要通过 v-model:自定义的prop名称
的方式进行组件上的双向数据绑定:
html
<MyCounter v-model:numb="num" />
同时,子组件中生成的 emit
事件名称也变成了 update:自定义的prop名称
的格式:

prop 校验
可通过 defineModel()
的第 2 个参数设置 prop 的校验规则:
js
const count = defineModel('numb', { required: true, type: Number })
🚨 注意 🚨
如果为 defineModel
prop 设置了一个 default
值且父组件没有为该 prop 提供任何值,会导致父组件与子组件之间不同步。因此不推荐在这里使用 default
选项设置默认值。
设置 ref 名称
在子组件中调用 defineModel()
之后,它的返回值就是转存的响应式的 ref 数据。例如:
js
const count = defineModel('numb')
上面代码中的常量 count
就是 ref 的名称:

想要修改 ref 的名称,只需要重新给这个常量命名即可~
多个 v-model 绑定
如果想让子组件支持绑定多个 v-model 数据,则调用 defineModel()
的时候必须要传入第 1 个参数,这是区分多个 v-model 的唯一标识。
子组件的代码如下:
js
<script setup>
const count = defineModel('numb', { required: true, type: Number, default: 2 })
const message = defineModel('msg')
</script>
<template>
<h3>count 的值是:{{ count }}</h3>
<button @click="count--">-</button>
<button @click="count++">+</button>
<hr />
<h3>message 的值是:</h3>
<!-- 由于 message 是局部的 ref, -->
<!-- 因此可以和 input 元素进行双向数据绑定 -->
<input type="text" v-model="message" />
</template>
父组件的代码如下:
vue
<script setup>
import MyCounter from './components2/MyCounter.vue'
import { ref } from 'vue'
const num = ref(0)
const info = ref('hello')
</script>
<template>
<h1>父组件中的 num 值是:{{ num }}</h1>
<p>info 的值是:{{ info }}</p>
<button @click="num--">-</button>
<button @click="num++">+</button>
<hr />
<!-- 这里通过 prop 的名称进行双向绑定的区分 -->
<MyCounter v-model:numb="num" v-model:msg="info" />
</template>