文章目录
- 什么是组件通信
- 不同的组件关系 和 组件通信方案分类
- 组件关系分类:
- 组件通信方案分类
- 父子通信流程图:
- 父传子
- 子传父
- 非父子通信 (拓展) - event bus 事件总线
- 非父子通信 (拓展) - provide & inject
- 深入学习prop属性
- prop接收多个值
- props 是只读的
- props 校验
- props 的 required 必填项
- props 的 default 默认值
- props 的 type 值类型
- 自定义校验validator属性
- prop & data、单向数据流
- 注意 我遇到一个Bug大家可以注意一下。
什么是组件通信
组件通信, 就是指 组件与组件 之间的数据传递。
- 组件的数据是独立的,无法直接访问其他组件的数据。
- 想用其他组件的数据怎么办呢? → 答:组件通信
不同的组件关系 和 组件通信方案分类
组件关系分类:
- 父子关系
- 非父子关系
组件通信方案分类
对于父子关系使用 props 和 $emit
非父子关系 1. provide & inject
和 2. eventbus
两种解决方案
对于很复杂的场景比如(爷爷孙子通信):使用Vuex
父子通信流程图:
- 父组件通过 props 将数据传递给子组件
- 子组件利用 $emit 通知父组件修改更新
父传子
运行效果
子传父
运行效果
点击按钮后,子组件改变父组件的值,父组件回过来影响子组件的值。
<!-- 子组件 -->
<template><div>Son - {{ title }}<!-- 定义触发事件名,父组件要和这个保持一致 --><button @click="changeTitle(NewTitle)">Change Title</button></div>
</template><script>
export default {props: ['title'],data () {return {// 子组件内部数据NewTitle: '子传父消息更新子父组件!!'}},methods: {changeTitle (args) {// $emit(触发事件, 传递参数)this.$emit('changeTitle', args)}}
}
</script>
<style></style>
<!-- 父组件 -->
<template><div>father <!-- @子组件触发事件名="父组件方法名" --><Son :title="msg" @changeTitle="handleChange"></Son></div>
</template>
<script>
import Son from './components/Son.vue'
export default {data(){return{msg:'父传子的信息',}},methods:{// 父组件方法(子组件传来的参数)handleChange(NewTitle){this.msg = NewTitle}},components:{Son,}
}
</script>
<style></style>
非父子通信 (拓展) - event bus 事件总线
作用:非父子组件之间,进行简易消息传递。(复杂场景 → Vuex)
-
创建一个都能访问到的事件总线 (空 Vue 实例) → utils/EventBus.js
import Vue from 'vue' const Bus = new Vue() export default Bus
-
A 组件(接收方),监听 Bus 实例的事件,
created () {Bus.$on('sendMsg', (msg) => {this.msg = msg}) }
-
B 组件(发送方),触发 Bus 实例的事件
Bus.$emit('sendMsg', '这是一个消息')
代码目录
代码演示
// EventBus.js
import Vue from 'vue'
const Bus = new Vue()
export default Bus
<template>
<!-- App.vue --><div class="app"><BaseA></BaseA><BaseB></BaseB><BaseC></BaseC></div>
</template><script>
import BaseA from './components/BaseA.vue'
import BaseB from './components/BaseB.vue'
import BaseC from './components/BaseC.vue'
export default {components:{BaseA,BaseB,BaseC}
}
</script><style></style>
<!-- BaseA.vue -->
<template><div class="base-a">我是A组件(接受方)<p>{{msg}}</p> </div>
</template><script>
import Bus from '../utils/EventBus'
export default {data() {return {msg: '',}},created() {Bus.$on('sendMsg', (msg) => {// console.log(msg)this.msg = msg})},
}
</script><style scoped>
.base-a {width: 200px;height: 200px;border: 3px solid #000;border-radius: 3px;margin: 10px;
}
</style>
<!-- BaseB.vue -->
<template><div class="base-b"><div>我是B组件(发布方)</div><button @click="sendMsgFn">发送消息</button></div>
</template><script>
import Bus from '../utils/EventBus'
export default {methods: {sendMsgFn() {Bus.$emit('sendMsg', '今天天气不错,适合旅游')},},
}
</script><style scoped>
.base-b {width: 200px;height: 200px;border: 3px solid #000;border-radius: 3px;margin: 10px;
}
</style>
<!-- BaseC.vue -->
<template><div class="base-c">我是C组件(接受方)<p>{{msg}}</p> </div>
</template><script>
import Bus from '../utils/EventBus'
export default {data() {return {msg: '',}},created() {Bus.$on('sendMsg', (msg) => {// console.log(msg)this.msg = msg})},
}
</script><style scoped>
.base-c {width: 200px;height: 200px;border: 3px solid #000;border-radius: 3px;margin: 10px;
}
</style>
运行结果
点击发送消息
非父子通信 (拓展) - provide & inject
provide & inject 作用:跨层级共享数据。
- 父组件 provide 提供数据
export default {provide () {return {// 普通类型【非响应式】color: this.color, // 复杂类型【响应式】userInfo: this.userInfo, }} }
- 子/孙组件 inject 取值使用
export default {inject: ['color','userInfo'],created () {console.log(this.color, this.userInfo)} }
讲解案例目录
讲解案例代码
<template>
<!-- App.vue --><div class="app">我是APP组件<button @click="change">修改数据</button><SonA></SonA><SonB></SonB></div>
</template><script>
import SonA from './components/SonA.vue'
import SonB from './components/SonB.vue'
export default {provide() {return {// 简单类型 是非响应式的color: this.color,// 复杂类型 是响应式的userInfo: this.userInfo,}},data() {return {color: 'pink',userInfo: {name: '张三',age: 18,},}},methods: {change() {this.color = 'red'this.userInfo.name = '李四'this.userInfo.age = 20},},components: {SonA,SonB,},
}
</script><style>
.app {border: 3px solid #000;border-radius: 6px;margin: 10px;
}
</style>
<template>
<!-- SonA.vue --><div class="SonA">我是SonA组件<GrandSon></GrandSon></div>
</template><script>
import GrandSon from '../components/GrandSon.vue'
export default {components:{GrandSon}
}
</script><style>
.SonA {border: 3px solid #000;border-radius: 6px;margin: 10px;height: 200px;
}
</style>
<template>
<!-- SonB.vue --><div class="SonB">我是SonB组件</div>
</template><script>
export default {}
</script><style>
.SonB {border: 3px solid #000;border-radius: 6px;margin: 10px;height: 200px;
}
</style>
<template>
<!-- GrandSon.vue --><div class="grandSon">我是GrandSon{{ color }} -{{ userInfo.name }} -{{ userInfo.age }}</div>
</template><script>
export default {inject: ['color', 'userInfo'],
}
</script><style>
.grandSon {border: 3px solid #000;border-radius: 6px;margin: 10px;height: 100px;
}
</style>
运行结果
点击修改数据
深入学习prop属性
什么是 prop
Prop 定义:组件上 注册的一些 自定义属性
Prop 作用:向子组件传递数据
特点:
- 可以 传递 任意数量 的prop
- 可以 传递 任意类型 的prop
prop接收多个值
<template><div><h3>子组件</h3><p>姓名:{{username}}</p><p>年龄:{{age}}</p><p>是否单身:{{isSingle?'是':'否'}}</p><p>汽车:{{"车类型:" + car.name + ", 价格:" + car.price}}</p><p>爱好:{{hobby[0] + "," + hobby[1] + "," + hobby[2]}}</p></div>
</template><script>
export default {props: ['username','age','isSingle', 'car','hobby'],}
</script>
<style></style>
<template><div><h1>父组件</h1><Son:username="username":age="age":isSingle="isSingle":car="car":hobby="hobby"></Son></div>
</template>
<script>
import Son from './components/Son.vue'
export default {data(){return{username:'小帅',age:18,isSingle:true,car: {name:'奔驰',color:'red',price:100000},hobby:['吃饭','睡觉','打豆豆'],}},components:{Son,}
}
</script>
<style></style>
props 是只读的
结果如下
点击增加
[Vue warn]: Avoid mutating a prop directly since the value
will be overwritten whenever the parent component re-renders.
Instead, use a data or computed property based on the prop's value.
Prop being mutated: "count"
直接修改 props 是高风险行为,尽管在简单场景下可能暂时“有效”,但会导致数据流混乱和潜在 Bug。下面打开调试工具发现,虽然子组件更新了但是父组件并没有改变,存在风险。可以通过子传父进行修改。
props 校验
思考:组件的 prop 可以乱传么?
作用:为组件的 prop 指定验证要求,不符合要求,控制台就会有错误提示 → 帮助开发者,快速发现错误
语法:
① 类型校验
② 非空校验
③ 默认值
④ 自定义校验
语法1
props: {校验的属性名: 类型 // Number String Boolean ...
},
语法2
props: {校验的属性名: {type: 类型, // Number String Boolean ...required: true, // 是否必填 默认是非必选。default: 默认值, // 默认值validator (value) {// 自定义校验逻辑return 是否通过校验}}
},
props 的 required 必填项
<template><div><h1>父组件 </h1><Son></Son></div>
</template>
<script>
import Son from './components/Son.vue'
export default {data(){return{count:1,}},components:{Son,}
}
</script>
<style></style>
<template><div><h3>子组件</h3>{{ count }}</div>
</template>
<script>
export default {props: {count: {type: Number,default: 1,required: true,validator: function (value) {if (value < 0) {console.error("count不能小于0");return false} else {return true}}}},
}
</script>
<style></style>
运行结果
props 的 default 默认值
当上面的必填项为false时,此时默认值是1;
运行结果
props 的 type 值类型
运行结果
自定义校验validator属性
结果
prop & data、单向数据流
共同点:都可以给组件提供数据。
区别:
- data 的数据是自己的 → 随便改
- prop 的数据是外部的 → 不能直接改,要遵循 单向数据流
单向数据流:父级 prop 的数据更新,会向下流动,影响子组件。这个数据流动是单向的。口诀:谁的数据谁负责这就是我们上面讲的如何结果prop的可读但不可修改的问题。
<template><div><h3>子组件</h3><button @click="sub">-1</button>{{ count }}<button @click="add">+1</button></div>
</template>
<script>
export default {props: {count: {type: Number,default: 1,required: false,validator: function (value) {if (value < 0) {console.error("count不能小于0");return false} else {return true}}}},methods: {sub(){// 这里不能直接修改props的值,需要通过事件通知父组件this.$emit('sub', this.count-1)},add(){// 这里不能直接修改props的值,需要通过事件通知父组件this.$emit('add', this.count+1)}}
}
</script>
<style></style>
<template><div><h1>父组件 </h1><!-- 父级 prop 的数据更新,会向下流动,影响子组件。这个数据流动是单向的。 --><Son :count="fcount" @sub="subCount" @add="addCount"></Son></div>
</template>
<script>
import Son from './components/Son.vue'
export default {data(){return{fcount: 0,}},methods:{subCount(newCount){this.fcount=newCountconsole.log(newCount);},addCount(newCount){this.fcount=newCount}},components:{Son,}
}
</script>
<style></style>
注意 我遇到一个Bug大家可以注意一下。
这个我遇到一个bug,大家可以看看为什么我点击+1或者-1不生效。
<template><div><h3>子组件</h3><button @click="sub">-1</button>{{ newCount }}<button @click="add">+1</button></div>
</template>
<script>
export default {props: {count: {type: Number,default: 1,required: false,validator: function (value) {if (value < 0) {console.error("count不能小于0");return false} else {return true}}}},data() {return {newCount: this.count}},methods: {sub(){this.$emit('sub', this.newCount - 1)},add(){this.$emit('add', this.newCount + 1)}}
}
</script>
<style></style>
<template><div><h1>父组件 </h1><Son :count="fcount" @sub="subCount" @add="addCount"></Son></div>
</template>
<script>
import Son from './components/Son.vue'
export default {data(){return{fcount: 0,}},methods:{subCount(newCount){this.fcount=newCount},addCount(newCount){this.fcount=newCount}},components:{Son,},components:{Son,}
}
</script>
<style></style>
问题核心分析
子组件 newCount 未同步更新
- 现象:父组件通过 :count=“fcount” 传递数据给子组件,但子组件只在初始化时通过 data() { newCount: this.count } 赋值一次。当父组件更新 fcount 时,子组件的 newCount 不会自动更新。
- 后果:点击按钮时,子组件基于旧值计算 newCount±1,导致父组件接收到的值始终滞后。
结果办法
<template><div><h3>子组件</h3><button @click="sub">-1</button>{{ newCount }}<button @click="add">+1</button></div>
</template>
<script>
export default {props: {count: {type: Number,default: 1,required: false,validator: function (value) {if (value < 0) {console.error("count不能小于0");return false} else {return true}}}},data() {return {// 父组件 默认让上面prop中的值变化。但是不会更新这里newCount的值。// 也就是说父组件更新 fcount=1,会使prop中的值更新,但子组件 newCount 仍为 0;newCount: this.count}},methods: {sub(){// 先改变自己的数据,然后再去改变父组件的数据this.newCount = this.newCount - 1this.$emit('sub', this.newCount)},add(){// 先改变自己的数据,然后再去改变父组件的数据this.newCount = this.newCount + 1this.$emit('add', this.newCount)}}
}
</script>
<style></style>
<template><div><h1>父组件 </h1><Son :count="fcount" @sub="subCount" @add="addCount"></Son></div>
</template>
<script>
import Son from './components/Son.vue'
export default {data(){return{fcount: 0,}},methods:{subCount(newCount){this.fcount=newCount},addCount(newCount){this.fcount=newCount}},components:{Son,}
}
</script>
<style></style>