前言
本章我们来学习Vue组件通信中的可以算是所有内容,在此之前,您最好掌握Vue的基础语法、指令等内容,同时也建议您查看我其他的文章进行补充。
组件通信
父子组件关系
通过上图顺带给大家说明了父子组件的实现原理,以及组件间传值传DOM的实现思路,那么我们看看Vue的代码来感受一下
父向子传值
模板部分(此处传值也能使用组件内的变量)
<div id="app">
<!-- 传递一个字符串常量haha -->
<son v-bind:text="'haha'"/>
</div>
js部分
// 子组件
var Son = {
// 要接收的字段名称
props:['text'],
template:`
<div>
{{ text }}
</div>`
};
Vue.component('son',Son);
// 父组件
new Vue({
el:'#app'
});
显示结果很显然是子组件的haha
子向父通信
试想一种情况,由父组件控制子组件的显示,而从子组件中点击X来实现子组件的隐藏,那么实际的控制权确实在父组件
要处理这个问题,大家第一反应应该就是相当通过子组件的点击事件,拿到父组件内控制显示与隐藏的变量并更改就可以了
代码如下
// 隐藏按钮点击函数
methods:{
clickChild(){
this.$parent.isShow = false;
}
}
实现是对的,不过
在框架中,我们最好抛开以前的DOM思想,否则我们会无法体会到框架带给我们的思想,而永远沉寂在面向过程中。。。
事件通信机制
在Vue中我们用@xxx来给原生DOM元素绑定事件,比如:click、input等。。
那么同时,我们也可以给子组件绑定自定义事件,不论他叫什么名字。举例:
<!-- 父组件 -->
<son v-show="isShow" @callme="callmeFn"/>
// 父组件js
methods:{
callmeFn(params){
this.isShow = false;
}
}
接下来子组件只用触发这个事件就好了,从低耦合的角度来说,是不是父子各玩各的呢?
// 子组件点击函数中,触发父组件为其绑定的事件
this.$emit('callme','还可以传递参数')
子向父值同步扩展
对于刚才的例子,我们实际上无需父组件和子组件函数的执行,只需要在子组件点击时同步父组件的isShow的值即可,那么可以这样写
<!-- 父组件内 -->
<son v-show="isShow" :isShow.sync="isShow"/>
从子组件中的代码你可以看出原理还是基于事件
// 子组件内
this.$emit('update:isShow','value')
通过Vue提供的事件对象来实现多层级别的组件通信
要做的只有类似这样3步
- 创建一个共同通信的对象
let connector = new Vue();
- 在要触发事件的函数中注册事件
connector.$on('事件名',function(params){ });
- 在目标事件中触发以上事件
connector.$emit('事件名','value');
事件原理实现
大家对于on,emit可能已经看得比较痛苦了,有懵逼的吧?那话不多说,直接上源码实现,相信会让你感觉好一点
var store = {
once:{},
fns:{},
on(key,fn,isOnce){
this.fns[key]?this.fns[key].push(fn):this.fns[key] = [fn];
},
emit(key,value){
let t = this.fns[key];
let o = this.once[key];
if (!t)return;
t.forEach(fn=>{
fn(value);
});
if (o) this.off(key);
},
off(key) {
delete this.fns[key];
},
once(key,fn){
this.once[key] = true;
this.on(key,fn);
}
}
原理其实就是一个对象用key存储着事件名,用value存储着函数!
传递DOM元素
看下面的功能,9宫格的6个,看看这些组件样子一样却又各不相同,因此,我们可以尝试由父组件给他们不同的DOM来体现他们的不同,子组件只需要做一些对他们公共特点的编写即可
如果子元素的大部分所有内容由父元素来定制,子组件内尽量单一、简单,该组件就算是低耦合、高内聚了。
那么子组件中给父组件留下的内容区域如何来实现呢? 答案是:slot(插槽)
slot实现步骤
- 在子组件中留坑
<!-- 子组件 -->
<div>
<!--一个默认的坑-->
<slot></slot>
</div>
- 在父组件中传入DOM
<!-- 父组件 -->
<!-- 使用子组件 -->
<son>
<!--传递的DOM-->
<template v-slot:default>
<button>给子的按钮</button>
</template>
</son>
- 当然也可以具名传递
<!-- 子组件 -->
<div>
<!-- 定制上半部分 -->
<slot name="head"></slot>
<!-- 定制下半部分 -->
<slot name="foot"></slot>
</div>
- 父组件也需要说清楚名字
<!-- 父组件 -->
<!-- 使用子组件 -->
<son>
<!--传递的DOM-->
<template v-slot:head>
<button>给头的按钮</button>
</template>
<template v-slot:foot>
<button>给底部的按钮</button>
</template>
</son>
最后说说一般人说不清楚的内容
思路整理: 这一组ul+li我们把他想象成一个组件,他接收一个数组,通过v-for帮我们渲染,数据如下
// 父组件数据
todos: [
{
id: 0,
text: 'ziwei0',
isComplete: true
},
{
text: 'ziwei1',
id: 1,
isComplete: true
},
{
text: 'ziwei2',
id: 2,
isComplete: false
},
{
text: 'ziwei3',
id: 3,
isComplete: false
}
];
父组件把该对象传递给子组件
<son :todos="todos"></son>
剩下的就交给子组件,让它来处理具体的业务吧! 对吗?
不是说好了,让子组件功能尽量单一,高内聚、低耦合的么,它都处理业务了,还谈啥公共组件呢,复用性不高,pass!
因此我们将业务判断功能留给父组,子组件这么写
<!--子组件-->
<ul>
<li v-for="todo in todos" :key="todo.id">
<slot>
</slot>
</li>
</ul>
那么问题来了,父组件是不是要传递DOM作为slot的值呢?
<!--父组件-->
<son :todos="todos">
<template v-slot:default>
<span>✓</span>
<span>内容</span>
</template>
</son>
糟糕,父组件把todos都传递进去了,可现在要通过每一个todo来做业务处理啊,看张图
因此,我们使用2.6的新功能slotProps,看slot 传值
<!--子组件-->
<ul>
<li v-for="todo in todos" :key="todo.id">
<slot :todo="todo">
</slot>
</li>
</ul>
看父组件如何接收,看template那里(固定写法) slotProps相当于内置变量
<!--父组件-->
<son :todos="todos">
<template v-slot:default="slotProps">
<span v-if="slotProps.todo.isComplete">✓</span>
<span>{{slotProps.todo.text}}</span>
</template>
</son>
搞定!如果你喜欢我的文章,可以关注我,如果忍不住想天天看到我,可以联系我加入我们的前端讨论群