VUE之组件(父子与非父子通信)

本文深入讲解Vue中组件通信的各种方式,包括父子组件通信、跨级组件通信和兄弟组件通信,探讨了不同场景下的最佳实践,如props、$emit、$attrs、$listeners及bus总线的使用。

组件组织

  • 通常一个应用会以一棵嵌套的组件树的形式来组织。
    在这里插入图片描述
  • 日常开发里,我们可以按照功能将一个网页拆分成很多个部分,每个部分就是一个组件。
  • 例如,可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的子组件。
    在这里插入图片描述

前言

  • 有人可能会问:为什么要学习组件通信
  • 原因:
  • 在实际开发中我们经常会遇到在一个自定义组件中要使用其他自定义组件,这就需要一个父子组件关系,进而引出互相通信的问题。

父子组件通信

  • 父子组件传值通信
  • 假设第二层组件想和第一层组件进行通信,分析其传值过程
  • 1、(第一层向第二层传值):父组件通过props传值到子组件,如此便实现父子组件向下通信
  • 2、(第二层向第一层传值):子组件通过触发自定义事件向父组件传值,如此便实现了父子组件向上通信
    在这里插入图片描述
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<script src="js/vue-2.6.9.min.js"></script>
	</head>
	<body>
		<div class="demo">
			我是父级我有{{moneyValue}}块钱
			<jiaojiao :money="moneyValue" @change="changeFn"></jiaojiao>
		</div>
	</body>
	<script type="text/javascript">
		Vue.component('jiaojiao',{
			props:{
				money:Number
			},
			data(){
				return{
					childMoney:this.money
				}
			},
			template:`
				<div @click='cut'>
					我是子级,我继承了{{childMoney}}块钱
				</div>
			`,
			methods:{
				cut(){
					this.childMoney--
					this.$emit('change',this.childMoney)
				}
			}
		})
		var demo = new Vue({
			el:'.demo',
			data:{
				moneyValue:5000
			},
			methods:{
				changeFn(option){
					console.log(option)
					this.moneyValue = option
				}
			}
		})
	</script>
</html>

跨级组件通信

  • 跨级组件传值通信
  • 假设第三层组件想和第一层组件进行通信
  • 1、(第一层组件向第三层组件传值)—禁止第一层直接传值到第三层,需要逐层传递,即1→2→3,此时便可以实现跨级组件向下通信
  • 2、(第三层组件向第一层组件传值)—通过触发自定义事件将数据传递至第二层,然后第二层通过事件触发传递至第一层,如此逐层传递,便可以实现跨级组件向上通信
    在这里插入图片描述
  • 逐级上传代码:

<!DOCTYPE html>
<html>
<head>
	<title>demo</title>
	<style type="text/css">
		
	</style>
	<script src="./vue-2.6.9.min.js"></script>
</head>
<body>
	<div id="app">
		我是一级{{moneyValue}}
		<er :money="moneyValue" @pass="passParent"></er>
	</div>
	<script type="text/javascript">
		Vue.component('er',{
			inheritAttrs:false,
			template:`
				<div class="er">
					我是二级组件<san v-bind="$attrs"></san>
				</div>
			`,
			mounted(){
				this.$emit('pass')
			}
		})
		Vue.component('san',{
			data(){
				return {
					sanMoney:this.$attrs.money
				}
			},
			template:`
				<div class="san">
					我是三级组件,我从祖父级获取{{sanMoney}}
				</div>
			`,
			mounted(){
				this.$emit('pass')
			}
		})
		var app = new Vue({
			el:'#app',
			data:{
				moneyValue:600
			},
			methods:{
				passParent(){
					console.log('返回祖父组件')
				}
			}
		});
	</script>
</body>
</html>
  • 监听上传代码:

<!DOCTYPE html>
<html>
<head>
	<title>demo</title>
	<style type="text/css">
		
	</style>
	<script src="./vue-2.6.9.min.js"></script>
</head>
<body>
	<div id="app">
		我是一级{{moneyValue}}
		<er :money="moneyValue" @pass="passParent"></er>
	</div>
	<script type="text/javascript">
		Vue.component('er',{
			inheritAttrs:false,
			template:`
				<div class="er">
					我是二级组件<san v-bind="$attrs" v-on="$listeners"></san>
				</div>
			`
		})
		Vue.component('san',{
			data(){
				return {
					sanMoney:this.$attrs.money
				}
			},
			template:`
				<div class="san" @click="listenerSan">
					我是三级组件,我从祖父级获取{{sanMoney}}
				</div>
			`,
			methods:{
				listenerSan(){
					this.sanMoney--;
					this.$listeners.pass(this.sanMoney)
				}
			},
			mounted(){
				
			}
		})
		var app = new Vue({
			el:'#app',
			data:{
				moneyValue:600
			},
			methods:{
				passParent(option){
					console.log(option)
					this.moneyValue = option;
				}
			}
		});
	</script>
</body>
</html>

兄弟组件通信

  • 兄弟组件传值通信
  • 假设第三层组件想和同层的另一个组件进行通信。
  • 如果还是逐层传递至第二层,再由第二层传递至第三层,此时代码量十分累赘复杂,这也不符合Vue的定义(Vue是一个轻量级的视图层框架)。
    在这里插入图片描述
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>兄弟组件通信</title>
		<script src="js/vue-2.6.9.min.js"></script>
	</head>
	<body>
		<div class="demo">
			<my-btn :count="4"></my-btn>
			<my-btn :count="2"></my-btn>
		</div>
	</body>
	<script type="text/javascript">
		 /*使用空Vue实例作为中央事件总线new Vue(),并注册全局bus */
		Vue.prototype.$bus = new Vue();
		Vue.component('my-btn',{
			props:{
				count:Number
			},
			data(){
				return{
					childCount:this.count
				}
			},
			template:`
				<button @click='handleClick'>{{childCount}}</button>
			`,
			methods:{
				  /* 第1个参数为分发事件名,第2个参数是传递的参数 */
				handleClick(){
					this.$bus.$emit('change',this.childCount+1);
				}
			},
			mounted(){
				/*3、监听分发事件*/
				var _this = this;
				this.$bus.$on('change',function(option){
					 /*获取传递的参数并进行操作*/
					_this.childCount = option;
				})
			},
			 /*最好在组件销毁前-清除事件监听*/
			beforeDestroy(){
				this.$bus.$off('change');
			}
		});
		var demo =new Vue({
			el:'.demo'
		})
	</script>
</html>


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>demo</title>
    <style type="text/css">
        
    </style>
    <script type="text/javascript" src="./vue-2.6.9.min.js"></script>
</head>
<body>
    <div class="demo">
        <my-btn :count="6"></my-btn>
        <my-btn :count="2"></my-btn>
    </div>
    <script type="text/javascript">
        /*使用空Vue实例作为中央事件总线new Vue(),并注册全局bus */
        Vue.prototype.$bus = new Vue();
        Vue.component('my-btn',{
            data(){
                return {
                    childCount:this.count
                }
            },
            props:{
                count:Number
            },
            template:`<button @click="handleClick">按钮{{childCount}}</button>`,
            methods:{
                handleClick(){
                    /* 第1个参数为分发事件名,第2个参数是传递的参数 */
                    this.$bus.$emit('change',this.childCount+1);
                }
            },
            mounted(){
                var _this = this;
                _this.$bus.$on('change',function(option){
                    /*获取传递的参数并进行操作*/
                    console.log(option)
                    _this.childCount = option;
                })
            },
            /*最好在组件销毁前-清除事件监听*/
            beforeDestroy(){
                this.$bus.$off('change');
            }
        })
        /* 构造器 */
        var demo = new Vue({
            el:'.demo'
        })
    </script> 
</body>
</html> 

兄弟|非父子组件通信

  • 引用官方文档:
  • 因为基于组件树结构的事件流方式让人难以理解,并且在组件结构扩展的过程中会变得越来越脆弱。
  • 这种事件方式确实不太好,我们也不希望在以后让开发者们太痛苦。
  • 官方推荐的状态管理方案是 Vuex。不过如果项目不是很大,状态管理也没有很复杂的话,使用 Vuex
    有种杀鸡用牛刀的感觉,当然,这也是要根据自己的需求来的。
  • 本节介绍下另一种方法,即bus/总线/发布订阅模式/观察者模式来解决
  • 文档术语:
  • 通过使用事件中心,允许组件自由交流,这个集中式的事件中间件就是 Bus总线。
  • 语法:
  • 在组件中使用$emit, $on, $off 分别来分发、监听、取消监听事件
  • 首先定义两个兄弟组件,如下所示在这里插入图片描述
  • 要求:点击一个组件,另一个兄弟组件也发生改动,两者数值相等
  • 接下来结合案例,介绍下如何通过bus/总线/发布订阅模式/观察者模式来解决非父子组件通信。
  • 第一步(注册全局bus总线):
  • 创建一个Vue实例,将其赋值给Vue.prototype.bus
    在这里插入图片描述
  • 解析:在Vue的prototype类上挂载了一个bus属性,该属性指向Vue实例。只要在其后通过Vue实例创建组件,则每个组件都会有bus属性
  • 写法2:Vue bus的使用(兄弟|非父子组件传值)–>可以使用一个空的Vue实例作为中央事件总线new Vue(),此外也可以写成$bus
    在这里插入图片描述
  • 有人可能会问“为什么加$,作用是什么”?
  • 接下来简单介绍下$作用

$命名法则

  • 简介:
  • $ 是在 Vue
    所有实例中都可用的属性的一个简单约定。这样做会避免和已被定义的数据、方法、计算属性产生冲突,即避免命名冲突。接下来结合案例测试下
    在这里插入图片描述
  • 接下来,在data数据里声明个同名的变量,如下所示
    在这里插入图片描述
  • 修改代码如下所示
  • 将Vue挂载实例命名前,加上前缀$,用于避免和已被定义的数据、方法、计算属性产生冲突,即避免命名冲突。
    在这里插入图片描述
  • 接下来继续介绍兄弟组件通信
  • 再来梳理下需求,即当点击某个组件改变时,希望同时触发另外一个非父级组件改动,所以接下来需要在子组件绑定事件
    在这里插入图片描述
    第二步:通过中央事件线程$bus,分发事件属性$emit
    在这里插入图片描述
  • 解析:
  • this.$bus为中央事件总线程new Vue实例,通过$emit来分发事件,分发事件时可以携带参数option。
  • 子组件分发完毕后,其他组件需要进行监听接收分发的事件,此时可以借助生命周期钩子来实现。
  • 第三步:

结合生命周期钩子监听和接收分发的事件,监听事件属性$on
在这里插入图片描述

  • 结合生命周期钩子监听和接收分发的事件,监听事件属性$on
  • 接下来修改数据
    在这里插入图片描述
  • 结合生命周期钩子监听和接收分发的事件,监听事件属性$on。
  • 解决方案之前说过,利用子组件data,拷贝份父组件传递数据,后续即可维护自身数据,而不改动父组件数据
    在这里插入图片描述
  • 第四步:取消|清除监听事件,取消监听属性$off
  • 日常开发里,为了避免不要的BUG,建议在使用bus时,加上清除监听操作,代码如下
    在这里插入图片描述
  • 如果需要监听多个组件,只需要更改 bus 的 eventName
    在这里插入图片描述
  • 步骤小结:
  • 1、注册全局$bus
  • 使用空Vue实例作为中央事件总线new Vue(),注册全局$bus总线
  • 2、分发$emit:
  • 通过中央事件线程bus,A组件,分发事件属性bus,A组件,分发事件属性busAemit
  • 3、监听$on:
  • 结合生命周期钩子,B组件监听和接收分发的事件,监听事件属性$on。
  • 4、取消$off:
  • 取消|清除监听事件,取消监听属性$off

父子组件通信小结

  • 引用官网的一句话:
  • 父子组件的关系可以总结为 prop 向下传递,事件向上传递
  • 父组件通过 prop 给子组件下发数据,子组件通过事件给父组件发送消息,如下图所示:
    在这里插入图片描述
  • 如果需要子对子传参,可以先从传到父组件,再传到子组件,但这样写过于累赘。
  • 方案:
  • 为了便于开发,vue 推出了一个状态管理工具 vuex,可以很方便实现组件之间的参数传递,后期结合工程化介绍。
  • 官方推荐的状态管理方案是 Vuex。不过如果项目不是很大,状态管理也没有很复杂的话,使用 Vuex有种杀鸡用牛刀的感觉。所以本节介绍下另一种方法,即bus/总线/发布订阅模式/观察者模式来解决

跨级组件通信

  • 跨级通信方案
  • 使用attrs和attrs和attrslisteners实现祖孙组件之间的数据传递,也就是多重嵌套组件之间的数据传递
  • 版本注意:
  • 本方法针对vue2.4版本及以上,使用attrs和attrs和attrslisteners来实现的
  • 首先看下跨级组件通信自上而下步骤,案例如下
    在这里插入图片描述
  • 跨级组件通信自上而下
  • 祖父级:用于动态数据的绑定与事件的定义
  • 父级:通过设置v-bind="attrs"和v−on="attrs" 和v-on="attrs"von="listeners"来充当中间人
  • 孙子级:通过attrs和attrs和attrslisteners来获取数据和事件
  • 一般来说,自上而下的通信会涉及到props与$attrs,而自下而上的通信,都是通过事件触发,然后层层上传。
  • 方案一:
  • $emit逐层上传
    在这里插入图片描述
    在这里插入图片描述
  • 方案二:通过$listeners接收事件
    在这里插入图片描述
    在这里插入图片描述

常见组件通信BUG

  • (1)bus.$on监听多次绑定,造成事件多次触发
  • 问题描述:只要页面没有强制刷新,存在组件切换,bus.$on方法会被多次绑定,造成事件多次触发
  • 方案:注册的总线事件(Bus)要在组件销毁时(beforeDestroy/destroyed)卸载,否则会多次挂载,造成触发一次但多个响应的情况
    在这里插入图片描述
  • 方案2:在每次调用方法前先解绑事件bus.off,然后在重新绑定bus.off ,然后在重新绑定bus.offbus.on ,该做法不常用,简单了解即可。

总结

  • (1)数值传递大概可以分为两种,即父子传值与非父子传值
  • (2)非父子传值包含:跨级传值、兄弟传值
  • (3)父向子传递:props
  • (4)子向父传递:$emit
  • (5)兄弟之间传递:借助中间代理,即中央事件总线bus使用bus使用bus使emit, $on, $off 分别来分发、监听、取消监听事件
  • (6)跨级通信:$attrs可以获取父作用域传入的值(不包括props中的),$listeners相当于父作用域的事件监听器,那我们就可以用这两个属性实现祖孙之间的数据通信

以上就是VUE之组件(父子与非父子通信)的见解,如有疑问请随时联系小编!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值