什么是组件通信

vue组件中关系说明:
在这里插入图片描述
如上图所示, 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>

3、展示效果:
在这里插入图片描述

  • 但如果没有用插槽,则数据传递不进来,没有多云那一行

总结:如果子组件没有使用插槽,父组件如果需要往子组件中填充模板或者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

Vuex 核心

  • State:保存所有组件的共享状态
  • Getters:类似状态值的计算属性
  • Mutations:修改 State中状态值的唯一方法,里面包含状态变化监听器和记录器
  • Actions:用于异步处理 State中状态值,异步函数结束后调用Mutations
  • Modules:当一个 State 对象比较庞大时,可以将 State 分割成多个Modules 模块。

总结

1.可以实现任意组件的通信的方法有两个:事件总线Vuex,事件总线难维护数据但轻量,Vux维护数据方便但比较重量。
2.可以实现父与子孙跨越层级通信的方法也有两个:$attrs/$listenersprovide/inject$attrs/$listeners 具有响应性且可以双向通信, provide/inject 无响应性且只能单向通信(父传子)
3.只能实现父与子组件通信的方法有一个:props/emit,方法比较基础,适合只有父子组件通信的方法,若想跨层级通信需要中间组件做转发,比较麻烦。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐