如上图所示, A与B、A与C、B与D、C与E组件之间是父子关系; B与C之间是兄弟关系;A与D、A与E之间是隔代关系; D与E是堂兄关系(非直系亲属)
针对以上关系我们归类为:
父子组件之间通信
非父子组件之间通信(兄弟组件、隔代关系组件等)
因此介绍在不同的场景下如何选择有效方式实现的组件间通信方式,以更好理解组件间的通信。
组件通信的方式有哪些
1、props:用于父=》子组件通信
- 父组件通过props的方式向子组件传递数据,而子组件通过$emit 可以向父组件通信。
- 举例:子组件的props选项能够接收来自父组件数据。没错,仅仅只能接收,props是单向绑定的,即只能父组件向子组件传递,不能反向。而传递的方式也分为两种
1、静态传递
子组件通过props选项来声明一个自定义的属性,然后父组件就可以在嵌套标签的时候,通过这个属性往子组件传递数据了。
<!-- 父组件 -->
<template>
<div>
<h1>我是父组件!</h1>
<child message="我是子组件一!"></child> //通过自定义属性传递数据
</div>
</template>
<script>
import Child from '../components/child.vue'
export default {
components: {Child},
}
</script>
<!-- 子组件 -->
<template>
<h3>{
{message}}</h3>
</template>
<script>
export default {
props: ['message'] //声明一个自定义的属性
}
</script>
2、动态传递
我们已经知道了可以像上面那样给 props 传入一个静态的值,但是我们更多的情况需要动态的数据。这时候就可以用 v-bind 来实现。通过v-bind绑定props的自定义的属性,传递去过的就不是静态的字符串了,它可以是一个表达式、布尔值、对象等等任何类型的值。
<!-- 父组件 -->
<template>
<div>
<h1>我是父组件!</h1>
<child message="我是子组件一!"></child>
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<child v-bind:message="a+b"></child>
<!-- 用一个变量进行动态赋值。-->
<child v-bind:message="msg"></child>
</div>
</template>
<script>
import Child from '../components/child.vue'
export default {
components: {Child},
data() {
return {
a:'我是子组件二!',
b:112233,
msg: '我是子组件三!'+ Math.random()
}
}
}
</script>
<!-- 子组件 -->
<template>
<h3>{
{message}}</h3>
</template>
<script>
export default {
props: ['message']
}
</script>
总结: prop 只可以从上一级组件传递到下一级组件(父子组件),即所谓的单向数据流。而且 prop 只读,不可被修改,所有修改都会失效并警告。
2、自定义事件:@on,@emit
可以实现子给父通信即vm.$emit( event, arg )$emit绑定一个自定义事件event,当这个这个语句被执行到的时候,就会将参数arg传递给父组件,父组件通过@event监听并接收参数。
//父组件
<template>
<div>
<h1>{
{title}}</h1>
<child @getMessage="showMsg"></child>
</div>
</template>
<script>
import Child from '../components/child.vue'
export default {
components: {Child},
data(){
return{
title:''
}
},
methods:{
showMsg(title){
this.title=title;
}
}
}
</script>
//子组件
<template>
<h3>我是子组件!</h3>
</template>
<script>
export default {
mounted: function () {
this.$emit('getMessage', '我是父组件!')
}
}
</script>
3、全局事件总线eventBus:$bus 全能
对于比较小型的项目,没有必要引入 vuex 的情况下,可以使用 eventBus。
它的实现思想也很好理解,在要相互通信的两个组件中,都引入同一个新的vue实例,然后在两个组件中通过分别调用这个实例的事件触发和监听来实现通信。
//eventBus.js
import Vue from 'vue';
export default new Vue();
<!--组件A-->
<script>
import Bus from 'eventBus.js';
export default {
methods: {
sayHello() {
Bus.$emit('sayHello', 'hello');
}
}
}
</script>
<!--组件B-->
<script>
import Bus from 'eventBus.js';
export default {
created() {
Bus.$on('sayHello', target => {
console.log(target); // => 'hello'
});
}
}
</script>
4、pubsub-js
vue当中几乎不用(因为vue中有全局事件总线和这个第三方提供的库功能重复) 但全能
含义:消息订阅与发布
-(由于原生js实现较困难,推荐用第三方库pubsub-js (npm i pubsub-js)
理解:需要消息的人=》订阅消息subscribe,
发布消息的人=》发布消息publish,
实现:需要消息的人:import pubsub from "pubsub-js",并在
mouted(){
this.pubId=pubsub.subscribe('hello‘,function(msgName,data)
{console.log(‘有人发布了hello消息,hello消息的回调执行了’,msgName,data)
}
)
}
发布消息的人:import pubsub from "pubsub-js",在事件函数里
methods:{xxxx事件(){ pubsub.publish(‘hello’,666)}}
效果:有人发布了hello消息,hello消息的回调执行了 hello 666
取消订阅: pubsub.unsubscribe('this.pubId')(即需要指定哪个id的消息被取消订阅)
5、插槽
插槽
就是子组件中的提供给父组件使用的一个占位符,用<slot></slot> 表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的<slot></slot>标签。
1、以最简单插槽为例:在子组件中放一个占位符
//子组件
<template>
<div>
<h1>今天天气状况:</h1>
<slot></slot>
</div>
</template>
<script>
export default {
name: 'child'
}
</script>
2、在父组件中给这个占位符填充内容
//父组件
<template>
<div>
<div>使用slot分发内容</div>
<div>
<child>
<div style="margin-top: 30px">多云,最高气温34度,最低气温28度,微风</div>
</child>
</div>
</div>
</template>
<script>
import child from "./child.vue";
export default {
name: 'father',
components:{
child
}
}
</script>
展示效果:
- 但如果没有用插槽,则数据传递不进来,没有多云那一行
总结:如果子组件没有使用插槽,父组件如果需要往子组件中填充模板或者html, 是没法做到的。
插槽使用 - 具名插槽
描述:具名插槽其实就是给插槽取个名字。一个子组件可以放多个插槽,而且可以放在不同的地方,而父组件填充内容时,可以根据这个名字把内容填充到对应插槽中。代码如下:
1、子组件的代码,设置了两个插槽(header和footer):
<template>
<div>
<div class="header">
<h1>我是页头标题</h1>
<div>
<slot name="header"></slot>
</div>
</div>
<div class="footer">
<h1>我是页尾标题</h1>
<div>
<slot name="footer"></slot>
</div>
</div>
</div>
</template>
<script>
export default {
name: "child1"
}
</script>
<style scoped>
</style>
2、父组件填充内容, 父组件通过 v-slot:[name] 的方式指定到对应的插槽中
<template>
<div>
<div>slot内容分发</div>
<child1>
<template slot="header">
<p>我是页头的具体内容</p>
</template>
<template slot="footer">
<p>我是页尾的具体内容</p>
</template>
</child1>
</div>
</template>
<script>
import child1 from "./child1.vue";
export default {
name: "father1",
components: {
child1
}
}
</script>
<style scoped>
</style>
6、vuex
https://blog.csdn.net/weixin_51225684/article/details/120347418?spm=1001.2014.3001.5502
7、$attrs和$listeners
$attrs 以及 $listeners 的出现解决的就是:组件在其中传递 props 以及事件的过程中,不必在写多余的代码,仅仅是将 $attrs 以及 $listeners 向上或者向下传递即可。
inheritAttrs:默认值 true,继承所有的父组件属性(除 props 的特定绑定)作为普通的
HTML特性应用在子组件的根元素上,如果你不希望组件的根元素继承特性设置
inheritAttrs: false ,但是 class 属性会继承(简单的说,inheritAttrs:true
继承除 props 之外的所有属性;inheritAttrs:false 只继承 class style 属性)
$attrs–继承所有的父组件属性(除了 prop 传递的属性、class 和 style ),
一般用在子组件的子元素上
$listeners属性,它是一个对象,里面包含了作用在这个组件上的所有监听器,你就可以
配合 v-on="$listeners" 将所有的事件监听器指向这个组件的某个特定的子元素。(相当
于子组件继承父组件的事件)
示例
A组件(App.vue)
<template>
<div id="app">
<!-- 此处监听了两个事件,可以在B组件或者C组件中直接触发 -->
<child1 :pchild1="child1" :pchild2="child2" :pchild3="child3" @method1="onMethod1" @method2="onMethod2"></child1>
</div>
</template>
<script>
import Child1 from "./Child1.vue";
export default {
data() {
return {
child1:'1',
child2: 2,
child3:{
name:'child3'
}
};
},
components: { Child1 },
methods: {
onMethod1(msg1) {
console.log(`${msg1} running`);
},
onMethod2(msg2) {
console.log(`${msg2} running`);
},
},
};
</script>
B组件(Child1.vue)
<template>
<div class="child-1">
<h2>in child1</h2>
<p>props: {
{ pchild1 }}</p>
<p>$attrs: {
{ $attrs }}</p>
<hr/>
<!-- 通过 v-bind 绑定$attrs属性,C组件可以直接获取到A组件中传递下来的props
(除了B组件中props声明的) -->
<!-- C组件中能直接触发test的原因在于 B组件调用C组件时
使用 v-on 绑定了$listeners 属性 -->
<child2 v-bind="$attrs" v-on="$listeners"></child2>
</div>
</template>
<script>
import Child2 from "./Child2.vue";
export default {
data() {
return {
child1:'child1'
};
},
components: { Child2 },
props: {
pchild1:{
type:String
}
},
inheritAttrs: false,
mounted() {
this.$emit("method1",this.child1);
},
};
</script>
C 组件 (Child2.vue)
<template>
<div class="child-2">
<h2>in child2:</h2>
<p>props: {
{ pChild2 }}</p>
<p>$attrs: {
{ $attrs }}</p>
<p>pchild3Name: {
{ $attrs.pchild3.name }}</p>
<hr/>
</div>
</template>
<script>
export default {
data() {
return {
child2:'child2'
};
},
props: {
pChild2:{
type:String,
}
},
inheritAttrs: false,
mounted() {
this.$emit("method2",this.child2);
},
};
</script>
8、$parent和$children
$parent:子组件访问父组件
$children:父组件访问子组件
$parent 子组件获取父组件的数据、调用父组件的方法
父组件
<template>
<div class="about">
<MessageBox ref = 'mes' class="MessageBox"/>
<h2>父组件-表格</h2>
<el-table
:data="tableData"
style="width: 100%">
<el-table-column
prop="name"
label="姓名"
>
</el-table-column>
<el-table-column
prop="desc"
label="内容"
>
</el-table-column>
<el-table-column
prop="time"
label="时间">
</el-table-column>
</el-table>
</div>
</template>
<script>
import MessageBox from '../components/message.vue';
export default {
data() {
return {
tableData: [],
};
},
methods: {
fun() {
console.log('父组件的方法!!!')
}
},
components: {
MessageBox
},
};
</script>
<style scoped lang="scss">
.about{
width: 50vw;
margin: auto;
}
</style>
子组件
<template>
<div class="message-container">
<h2>子组件-留言板信息输入</h2>
<el-form ref="form" :model="form" label-width="80px">
<el-form-item label="姓名">
<el-input v-model="form.name" clearable ></el-input>
</el-form-item>
<el-form-item label="内容">
<el-input type="textarea" v-model="form.desc"></el-input>
</el-form-item>
<el-button type="primary" @click="addMes()">提 交</el-button>
</el-form>
</div>
</template>
<script>
export default {
name:'message',
data() {
return {
form:{
name:'',
desc:'',
}
};
},
methods: {
addMes(){
const obj = Object.assign(this.form,{time:this.getNowTime()});
this.$parent.tableData.push(obj);
// 获取父组件的数据
// this.$parent.tableData;
// 调用父组件的方法
// this.$parent.fun();
},
// 获取当前时间
getNowTime() {
let dtime = new Date();
let nian = dtime.getFullYear(),
yue = dtime.getMonth() + 1,
day = dtime.getDate(),
hours = dtime.getHours(),
fen = dtime.getMinutes(),
miao = dtime.getSeconds();
return nian + "-" + yue + "-" + day + " " + hours + ":" + fen + ":" + miao;
}
},
components: {
}
}
</script>
$children 父组件获取子组件的数据、调用子组件的方法
父组件
<template>
<div class="about">
<MessageBox ref = 'mes' class="MessageBox"/>
<h2>父组件-表格</h2>
<el-button type="primary" @click="addMes()">提 交</el-button>
<el-table
:data="tableData"
style="width: 100%">
<el-table-column
prop="name"
label="姓名"
>
</el-table-column>
<el-table-column
prop="desc"
label="内容"
>
</el-table-column>
<el-table-column
prop="time"
label="时间">
</el-table-column>
</el-table>
</div>
</template>
<script>
import MessageBox from '../components/message.vue';
export default {
data() {
return {
tableData: [],
};
},
methods: {
addMes() {
// 获取子组件的数据
const child_val = this.$children[0].form;
const obj = Object.assign(child_val,{time:this.getNowTime()});
this.tableData.push(obj);
// 调用子组件的方法
this.$children[0].fun();
},
// 获取当前时间
getNowTime() {
let dtime = new Date();
let nian = dtime.getFullYear(),
yue = dtime.getMonth() + 1,
day = dtime.getDate(),
hours = dtime.getHours(),
fen = dtime.getMinutes(),
miao = dtime.getSeconds();
return nian + "-" + yue + "-" + day + " " + hours + ":" + fen + ":" + miao;
},
},
components: {
MessageBox
},
};
</script>
<style scoped lang="scss">
.about{
width: 50vw;
margin: auto;
}
</style>
子组件
<template>
<div class="message-container">
<h2>子组件-留言板信息输入</h2>
<el-form ref="form" :model="form" label-width="80px">
<el-form-item label="姓名">
<el-input v-model="form.name" clearable ></el-input>
</el-form-item>
<el-form-item label="内容">
<el-input type="textarea" v-model="form.desc"></el-input>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
name:'message',
data() {
return {
form:{
name:'',
desc:'',
},
};
},
methods: {
fun() {
console.log('子组件的方法!!!')
}
},
components: {
}
}
</script>
9、provide和inject
provide:是一个对象,或者是一个返回对象的函数。里面呢就包含要给子孙后代的东西,也就是属性和属性值。
inject:一个字符串数组,或者是一个对象。属性值可以是一个对象,包含from和default默认值。
const Child = {
inject: {
foo: {
from: 'bar',
default: 'foo'
}
}
}
from表示在可用的注入内容中搜索用的 key,default当然就是默认值。
案例:
爷爷组件
<template>
<div id="app">
我是父组件:{
{message}}
<second></second>
</div>
</template>
<script>
import second from '../components/second.vue'
export default{
data(){
return{
message:'我们一起当前端攻城狮!'
}
},
provide(){ // provide是一个匿名函数,返回一个对象
return {
testmethods:this.testmethods,
message:this.message
}
},
methods:{
testmethods(){
console.log('调用了ProvideTest这个组件')
}
},
components:{
second
}
}
</script>
<style lang="less" scoped>
</style>
儿子组件
<template>
<div id="app">
我是父组件:{
{message}}
<second></second>
</div>
</template>
<script>
import second from '../components/second.vue'
export default{
data(){
return{
message:'我们一起当前端攻城狮!'
}
},
provide(){ // provide是一个匿名函数,返回一个对象
return {
testmethods:this.testmethods,
message:this.message
}
},
methods:{
testmethods(){
console.log('调用了ProvideTest这个组件')
}
},
components:{
second
}
}
</script>
<style lang="less" scoped>
</style>
孙组件:
<template>
<div id="app">
<p>third组件:{
{message}}</p>
</div>
</template>
<script>
export default{
data(){
return{
}
},
//inject:['message','testmethods'], 简写
inject:{ // 详细指定来源以及默认值
message:{
from:'message', //表示从组件ProvideTest传递过来的
//default:'message' //默认值
},
testmethods:{
form:'testmethods'
}
},
mounted() {
this.testmethods()
},
}
</script>
<style lang="less" scoped>
</style>