在 Vue 2 开发中,父子组件的数据同步是最高频的场景。很多新手容易在“单向数据流”和“双向绑定”之间绕晕。
今天我们通过两个具体的案例——“手动双向绑定”和“v-model 语法糖”,来彻底总结组件通信的核心要点。
案例一:手动实现双向绑定 (Props + Emit)
这是 Vue 最基础的通信方式,遵循“Props 向下,Events 向上”的原则。为了不违背单向数据流(子组件不能直接改 Props),我们需要在子组件里把 Props 转存为 Data。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>手动双向绑定</title> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> </head> <body>
<div id="app"> <h3>案例一:手动绑定</h3> <cpn :child-num="parentNum" @update-num="handleUpdate"></cpn>
<p>父组件的数据: {{ parentNum }}</p> </div>
<template id="cpn"> <div style="background: #eee; padding: 10px;"> <h4>Props值: {{ childNum }}</h4> <h4>Data值: {{ localNum }}</h4> <input type="text" :value="localNum" @input="onInput"> </div> </template>
<script> const cpn = { template: "#cpn", props: { childNum: Number }, data() { return { localNum: this.childNum }; }, methods: { onInput(e) { let val = e.target.value;
val = Number(val);
this.localNum = val;
this.$emit("update-num", val); } } };
const app = new Vue({ el: "#app", data() { return { parentNum: 100 }; }, components: { cpn }, methods: { handleUpdate(val) { this.parentNum = val; } } }); </script>
</body> </html>
|
核心逻辑解析
- Props 初始化 Data:在
data() 中使用 this.childNum 将父组件传来的值作为初始值。
- 修改本地 Data:用户输入时,先修改
this.localNum。
- 通知父组件:使用
this.$emit 告诉父组件“我也变了,你那边也改一下吧”。
案例二:使用 v-model (语法糖)
Vue 为了简化上面的操作,提供了 v-model。在 Vue 2 中,v-model 本质上就是 :value + @input 的缩写。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>v-model 语法糖</title> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> </head> <body>
<div id="app"> <h3>案例二:v-model 语法糖</h3>
<cpn v-model="parentNum"></cpn>
<p>父组件的数据: {{ parentNum }}</p> </div>
<template id="cpn"> <div style="background: #eef; padding: 10px;"> <h4>Props(value): {{ value }}</h4> <input type="text" :value="value" @input="onInput"> </div> </template>
<script> const cpn = { template: "#cpn", props: { value: Number }, methods: { onInput(e) { const val = Number(e.target.value);
this.$emit("input", val); } } };
const app = new Vue({ el: "#app", data() { return { parentNum: 666 }; }, components: { cpn } }); </script>
</body> </html>
|
核心逻辑解析
- 固定 Props 名:子组件必须接收一个叫
value 的 prop(这是 Vue 2 v-model 的默认规矩)。
- 固定事件名:子组件必须发射一个叫
input 的事件。
- 父组件超简洁:直接写
v-model,不用写 method 去接收,Vue 帮你自动处理赋值。
总结:Vue 2 组件通信的 4 个“死规定”
通过对比这两个案例,我们在写代码时必须牢记以下几点(考试/面试必问):
1. 单向数据流 (One-Way Data Flow)
- 死规定:绝对不能在子组件里直接修改 Props!
- 如果你在子组件写
this.dnum1 = 100,Vue 会在控制台报错。
- 做法:要么像案例一那样把 Props 转给 Data,要么像案例二那样直接 Emit 给父组件改。
2. 所有的 Data 必须是函数
- 死规定:
data() { return { ... } }。
- 如果写成对象
data: { ... },组件复用时数据会乱套。
- 坑点:
e.target.value 拿到的永远是 String。
- 做法:如果你需要数字,必须手动用
Number() 或 parseFloat() 转换,否则 1 + 1 会变成 '11'。
4. 这里的 this 不能省
- 在
<script> 标签里(methods, created 等),访问数据必须加 this.。
- 比如
this.$emit,this.value。漏了 this 就会报错找不到变量。
建议:
建议先熟练掌握案例一(手动绑定),因为它逻辑最清晰,能帮你理解数据是怎么流动的。等熟悉了之后,再使用 v-model 这种简写方式来提高开发效率!