组件的通信
为什么要进行组件通信?
组件是一个聚合体,将来项目要合并,那么必然各个组件之间需要建立联系,这个联系就是数据通信
一、 父子通信
业务逻辑:老爸给儿子2000生活费
- 属性绑定 + props选项
- props的值
- 数组 props: [‘money’]
- 对象 props: {money:Number}
- 验证函数 props: {money: {validator (val) {}}}
分析:
1.老爸给儿子2000,那么数据是在Father组件身上
2.数据以属性绑定的形式给
3.Son组件通过props选项来接收绑定在身上的属性
4.数据验证
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.js"></script>
</head>
<body>
<!-- 根实例 -->
<div id="app">
<Father/>
</div>
<template id='father'>
<div>
<!-- 跟子组件打交道的地方在这里 -->
<!-- 以属性绑定的形式给 -->
<Son :money='money'/>
</div>
</template>
<template id='son'>
<div>
<p>我一共有:{{money+n}}元</p>
</div>
</template>
</body>
<script>
// 业务: 老爸有2000,给儿子
//分析:1.老爸给儿子2000,那么数据是在Father组件身上
//Father组件
Vue.component('Father',{
template:'#father',
data(){
return {
money:2000
}
}
})
//Son组件
Vue.component('Son',{
data(){
return {
n:500
}
},
template:'#son',
//son组件通过props选项来接收绑定在身上的属性
props:['money'] //接收到的money就相当于data中定义的数据,可以直接使用
//属性验证
// props:{
// money:Number
// }
//验证函数
// props:{
// money:{
// // validator是一个验证函数,它必须有一个返回值,返回值是布尔值,可以进行正则判断
// validator(val){
// return val>3000
// }
// }
// }
})
new Vue({
el:'#app'
})
</script>
</html>
思考:但是父亲给儿子的2000元,一定是真的吗?
因此,一般我们还需要进行数据验证
1.属性验证:
//属性验证
props:{
money:Number
}
当Father组件中传递的数据money是字符串"2000"时,打印台会提示警告,不影响出结果,但是显示在页面上的数据会进行隐式转换,进行字符串的拼接,并不是理想得到的数据了
2.验证数据大小【 判断条件 】:
//验证函数
props:{
money:{
// validator是一个验证函数,它必须有一个返回值,返回值是布尔值,可以进行正则判断
validator(val){
return val>3000
}
}
}
同样,打印台也会提示警告,数据2000不满足大于3000这个条件
二、2. 子父通信
业务逻辑:父亲节: 儿子给老爸888红包
- 通过自定义事件来完成 $emit
分析:
1.给儿子定义数据
2.钱是儿子给父亲的,Son组件要定义一个give的方法
3.私房钱改变的方法是父组件的,通过自定义事件的方式传递给子组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.js"></script>
</head>
<body>
<div id="app">
<Father/>
</div>
<template id="father">
<div>
<p>我的私房钱:{{ sifangqian }}</p>
<!-- 自定义事件绑定的形式 相当于vm.$on('get',function () {}) -->
<!-- 声明一个事件 -->
<Son @get="addSifangqian"/>
</div>
</template>
<template id="son">
<div>
<button @click='give'>给</button>
</div>
</template>
</body>
<script>
// 业务: 父亲节了, 儿子给老爸888红包,老爸放到私房钱里面
Vue.component('Father',{
data(){
return {
sifangqian:500
}
},
methods:{//私房钱改变的方法
addSifangqian(val){
this.sifangqian += val
}
},
template:'#father',
})
Vue.component('Son',{
template:'#son',
data(){
return {
hongbao:888
}
},
methods:{
give(){//给红包的方法
this.$emit('get',this.hongbao)//触发自定义事件
}
}
})
new Vue({
el:'#app'
})
</script>
</html>
点击一次,由原先的500增加至1388
三、非父子组件通信
业务逻辑:姐打弟弟,弟弟哭
解决方式有两种:
- ref绑定 - 慎用 -> 性能不好
- event bus 事件总线 -> 无视层级嵌套
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.js"></script>
</head>
<body>
<div id="app">
<!-- 单项数据绑定,将父组件Root里的kick方法传给Sister组件 -->
<Sister :kick='kick'></Sister>
<!-- ref绑定可以获取到绑定的组件或是元素 -->
<!-- 这里的brother就是this.$refs.brother -->
<Brother ref='brother'></Brother>
</div>
<template id="sister">
<div>
<button @click='kick'>揍弟弟</button>
</div>
</template>
<template id="brother">
<div>
<img v-show='flag' src="https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1016202020,2298260494&fm=26&gp=0.jpg" alt="">
</div>
</template>
</body>
<script>
// 业务: 姐姐打弟弟,弟弟哭起来
//Sister组件需要拿到Brother组件上的changeFlag方法
//解决方案:通过共有的父组件Root来做,在父组件里获取到Brother组件的一切,然后写个方法传给Sister组件
Vue.component('Sister',{
template:'#sister',
props:['kick'] //props选项接受kick方法。然后直接调用
})
Vue.component('Brother',{
template:'#brother',
data(){
return {
flag:false
}
},
methods:{
changeFlag(){
this.flag=!this.flag
}
}
})
new Vue({
el:'#app',
methods:{
kick(){
console.log('this',this);
this.$refs.brother.changeFlag()
}
}
})
</script>
</html>
思考:Sister组件怎么才能拿到Brother组件上的changeFlag方法?
解决方案:通过共有的父组件Root来做,在父组件里获取到Brother组件的一切,然后写个方法传给Sister组件
问题:上述情况只存在一层嵌套关系,那如果是两个很多层嵌套里的子组件之间需要通信怎么解决呢?再一级一级向上找到公共的父组件?
显然不可取
解决方案:event bus 事件总线
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.js"></script>
</head>
<body>
<div id="app">
<Sister></Sister>
<Brother></Brother>
</div>
<template id="sister">
<div>
<button @click="kick"> 揍弟弟 </button>
</div>
</template>
<template id="brother">
<div>
<img v-show="flag" src="https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1016202020,2298260494&fm=26&gp=0.jpg" alt="">
</div>
</template>
</body>
<script>
// 业务: 姐姐打弟弟,弟弟哭起来
//为什么要选它作为中间人?因为eventBus里有$on和$emit两方法
const eventBus = new Vue()
Vue.component('Sister',{
template: '#sister',
methods: {
kick () {
eventBus.$emit('hit')//触发自定义事件
}
}
})
Vue.component('Brother',{
template: '#brother',
data () {
return {
flag: false
}
},
mounted () {
// mounted这个选项表示,组件挂载结束,也就是说大家可以在浏览器中看到界面了
// 虚拟dom渲染成了真实dom的时候
eventBus.$on('hit',() => {//发布自定义事件
this.flag = !this.flag
})
}
})
new Vue({
el: '#app',
})
</script>
</html>
通过一个中间人来进行操作,就可以无视层级嵌套的问题了。
有了这种方法,是不是一下子觉得上面那种方法不香了吧
四、跨组件通信
provide & inject ( Vue3.0 新增 )
provide() 和 inject() 可以实现嵌套组件之间的数据传递。这两个函数只能在 setup() 函数中使用。父级组件中使用 provide() 函数向下传递数据;子级组件中使用 inject() 获取上层传递过来的数据。
1.共享普通数据
App.vue根组件:
<template>
<div id="app">
<h1>App 根组件</h1>
<hr />
<LevelOne />
</div>
</template>
<script>
import LevelOne from './components/LevelOne'
// 1. 按需导入 provide
import { provide } from '@vue/composition-api'
export default {
name: 'app',
setup() {
// 2. App 根组件作为父级组件,通过 provide 函数向子级组件共享数据(不限层级)
// provide('要共享的数据名称', 被共享的数据)
provide('globalColor', 'red')
},
components: {
LevelOne
}
}
</script>
LevelOne.vue 组件:
<template>
<div>
<!-- 4. 通过属性绑定,为标签设置字体颜色 -->
<h3 :style="{color: themeColor}">Level One</h3>
<hr />
<LevelTwo />
</div>
</template>
<script>
import LevelTwo from './LevelTwo'
// 1. 按需导入 inject(注入)
import { inject } from '@vue/composition-api'
export default {
setup() {
// 2. 调用 inject 函数时,通过指定的数据名称,获取到父级共享的数据
const themeColor = inject('globalColor')
// 3. 把接收到的共享数据 return 给 Template 使用
return {
themeColor
}
},
components: {
LevelTwo
}
}
</script>
LevelTwo.vue 组件:
<template>
<div>
<!-- 4. 通过属性绑定,为标签设置字体颜色 -->
<h5 :style="{color: themeColor}">Level Two</h5>
</div>
</template>
<script>
// 1. 按需导入 inject
import { inject } from '@vue/composition-api'
export default {
setup() {
// 2. 调用 inject 函数时,通过指定的数据名称,获取到父级共享的数据
const themeColor = inject('globalColor')
// 3. 把接收到的共享数据 return 给 Template 使用
return {
themeColor
}
}
}
</script>
2. 共享 ref 响应式数据
如下代码实现了点按钮切换主题颜色的功能,主要修改了 App.vue 组件中的代码,LevelOne.vue 和 LevelTwo.vue 中的代码不受任何改变:
<template>
<div id="app">
<h1>App 根组件</h1>
<!-- 点击 App.vue 中的按钮,切换子组件中文字的颜色 -->
<button @click="themeColor='red'">红色</button>
<button @click="themeColor='blue'">蓝色</button>
<button @click="themeColor='orange'">橘黄色</button>
<hr />
<LevelOne />
</div>
</template>
<script>
import LevelOne from './components/LevelOne'
import { provide, ref } from '@vue/composition-api'
export default {
name: 'app',
setup() {
// 定义 ref 响应式数据
const themeColor = ref('red')
// 把 ref 数据通过 provide 提供的子组件使用
provide('globalColor', themeColor)
// setup 中 return 数据供当前组件的 Template 使用
return {
themeColor
}
},
components: {
LevelOne
}
}
</script>