一、举个栗子
<!-- 父组件 -->
<template>
<!-- 完整写法 -->
<ChildComponent
:modelValue="count"
@update:modelValue="newValue => count = newValue"
/>
<!-- 语法糖写法 -->
<ChildComponent v-model="count" />
</template>
<!-- 子组件 -->
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
function updateValue() {
const newValue = props.modelValue + 1
emit('update:modelValue', newValue) // 这里的 newValue 就是父组件的 $event
}
</script>
二、双向绑定原理
1. v-model 的本质
在 Vue 3 中,v-model 是语法糖,它实际上是以下形式的简写:
// 方法一
<ChildComponent
:modelValue="count"
@update:modelValue="newValue => count = newValue"
/>
//方法二
<ChildComponent
:modelValue="count"
@update:modelValue="count = $event"
/>
2. 数据流向
- 父 → 子:通过
modelValueprop 传递数据 - 子 → 父:通过
update:modelValue事件回传数据
3. 为什么这是双向绑定?
- 单向数据流:Vue 本质上仍然是单向数据流(父→子)
- 双向绑定效果:通过 props 向下传递 + 事件向上通知的组合,模拟出了双向绑定的效果
三、代码详细解释
父组件部分
count = ref(0)- 创建响应式数据v-model="count"- 等价于::modelValue="count"(传递数据给子组件)@update:modelValue="count = $event"(监听子组件更新)
子组件部分
defineProps(['modelValue'])- 声明接收父组件传递的 modelValue propdefineEmits(['update:modelValue'])- 声明将触发 update:modelValue 事件emit('update:modelValue', newValue)- 触发事件通知父组件更新 ,(注意此处的newValue 就是父组件中的$event,$event是 Vue 提供的特殊变量,表示事件对象或事件负载(payload))
四、与传统双向绑定的区别
传统双向绑定 (如 AngularJS)
- 真正的双向绑定
- 子组件直接修改父组件数据
- 数据流方向不明确,难以追踪
Vue 的"双向绑定"
- 实际上是单向数据流 + 事件机制的组合
- 数据修改必须通过事件显式通知
- 数据流清晰可追踪
- 更符合 Vue 的设计哲学
五、为什么推荐这种方式?
- 明确的数据流:清晰知道数据如何变化
- 更好的可维护性:所有状态变更都有迹可循
- 组件解耦:子组件不直接依赖父组件实现
- 灵活性:可以轻松添加验证或转换逻辑
六、文档参考
建议大家深入理解这种模式,因为它体现了 Vue 的核心设计理念:显式优于隐式。虽然看起来比直接双向绑定更"啰嗦",但这种设计带来了更好的可维护性和可预测性。