这十种组件间通信方式,你都掌握了吗

539 阅读4分钟

props通信

props通信是最基础最简单的一种通信方式,使用props进行组件间通信既可以用来父向子组件传递数据,也可以把子组件的数据传递给父组件。实际上这种通信方式没有任何的限制,它也能够实现爷孙、兄弟通信,只需要层层传递参数即可。但是若关系层级超过2层以上,就不推荐使用props通信了,面对这种场景我们可以使用全局事件总线更加方便地传递数据。

props通信通常都是由父组件给子组件传递数据,父组件传递的参数数据大致可以分为函数和非函数两种。当父组件给子组件传递非函数数据即纯数据时,子组件只能被动接收。而当父组件给子组件传递一个函数时,这时我们可以在子组件中调用这个函数,利用给这个定义在父组件里的函数传递形参,从而达到子组件给父组件传参的目的。

子组件在接收父组件传过来的参数时,也有三种方法接收。每种方法都有不同的特点,可以根据不同需求来选择使用

// 父组件
<template>
<div>
    <Son :money="money" :getmoney="getMoney" /> //父给子传参
</div>
</template>
<javascript>
export default {
    data(){
    	money:1000
    },
    methods:{
    	getMoney(money){
    		this.money +=money;
    	}
    }
}
</javascript>
---------
// 子组件 Son
<template>
	<ul>
        <li>{{money}}</li>
        <li>
            <button @click='getmoney(100)'></button>  //子给父传参
    	</li>
    </ul>
</template>
<javascript>
export default {
    //第一种props接收参数的形式,用数组形式接收数据
    props:['money'],
    props:{
    	getmoney:Fuction //利用getmoney这个函数参数,更新父组件的money
    }
    /* 第二种props接收参数的形式,限定props参数类型
    props:{
    	money:Number
    }
    第三种props接收参数的形式,限定参数类型同时指定默认值
    props:{
    	money:{
    		type:Number,
    		default:1
		}
	}
    */
}
</javascript>

路由传参里的props

在路由条目对象里配置props本质上就是将$route.params映射到props中。使用对象传递params参数时,路由条目的对象必须使用name属性。

{
	name: "search", //使用对象传params参数必须用name
	path: "/search/:keyword?", // ?表示keyword参数可传可不传
	component: Search,
	props: true // 默认为false,true时会将所有的params参数映射到props
	// props:(route)=>({keyword3:route.params.keyword,keyword4:route.query.keyword2})
},

自定义事件

在了解自定义事件之前我们需要明确什么是自定义事件。所谓自定义事件其实就是我们自己定义的事件,它和原生DOM事件有很大不同。

原生DOM事件的特点是:

  • 系统定义的,数量是固定的。就那些事件,事件名是固定的
  • 由系统(即浏览器)管理、触发
  • 回调函数是我们定义的,系统调用的
  • 回调函数的第一个参数是事件对象,是系统自动传入的

Vue自定义事件的特点是:

  • 自己定义的,数量是无限个,自己想定义多少定义多少,事件名随意取
  • 由我们自己管理、触发
  • 回调函数是我们定义的,系统调用的
  • 回调函数的参数是我们手动传入的,没传就没有

除了以上特点,判断一个事件是否是自定义事件的绝对准则是:所有绑定在组件标签上的事件都是自定义事件。若我们想将组件标签上的自定义事件转换为同名的原生DOM事件则需要在自定义事件后加上.native,例如@click.native。这样就把一个组件标签上的自定义事件click转换为同名的原生DOM事件click

在我们使用vue的过程中,绑定原生DOM事件监听会有两种情况:一是在html标签上绑定事件监听,二是在组件标签上绑定事件监听。当我们在组件标签上绑定原生DOM事件时,实际上是将事件绑定在了组件的根标签上,利用事件委托来监听所有子组件发生的事件。

// 父组件
<template>
<div>
    <Son @click.native="alert('呵呵呵')">
</div>
</template>
--------
// 子组件 Son
<template>
<div> // 父组件的原生DOM事件绑定在div上
	<h2>哈哈哈</h2>    
    <h3>嘿嘿嘿</h3>
</div>
</template>

同样地我们也可以将自定义事件绑定在html标签上,但是这样并没有意义。因为自定义事件要在标签内部使用$emit触发,而html标签无法进入内部触发$emit

利用自定义事件的特性,我们可以实现子向父通信传递参数

// 父组件
<template>
<div>
    <Son @click="test1" />
</div>
</template>
<javascript>
export default {
	data(){
    	
    },
    methods:{
    	test1(event){
    		alert(event);
    	}
    }
}
</javascript>
--------
// 子组件
<template>
	<div>
        <button @click="$emit('click','嘿嘿')">嘿嘿</button>
        <button @click="$emit('click','哈哈')">哈哈</button>
    </div>
</template>

PubSubJS

pubsub.js方式通信是让需要通信的两个组件分别引入该插件,然后再使用。

import pubSub from 'pubsub-js';
// 发布消息的组件中传递数据,就给数据的组件
pubSub.publish('消息名',数据对象);
//订阅消息的组件,即接收数据的组件
pubSub.subscribe('消息名',(接收数据的参数)=>{});

全局事件总线

全局事件总线本质上就是利用作用域链和自定义事件这两个机制来实现组件间通信。它可以用于任意组件之间进行数据通信。使用全局事件总线有三个步骤,分别是:

  1. 定义总线
  2. 在需要接收参数的组件里,让总线上绑定自定义事件
  3. 在传出数据的组件里,触发总线上的自定义事件

随便用一个对象去定义总线是无效的,成为事件总线的对象要满足两个条件:

  • 该对象能够被所有的组件实例找到
  • 该对象要能够使用$emit$on方法
// main.js 
Vue.prototype.$bus = new Vue() //定义事件总线

// Father.vue
this.$bus.$on('addUser',(data)=>{console.log(data)};) // 给事件总线绑定事件

// Son.vue
this.$bus.$emit('addUser',data) // 触发事件总线

全局事件总线的原理

全局事件总线原理

作用域插槽

根据作用域插槽的机制,我们可以实现子组件向父组件中传参。因为在作用域插槽中,数据的结构或样式是根据父组件决定的,而此时我们需要将在子组件中遍历过的数据重新传递给父组件,将改变的数据部分用slot包裹。在slot标签中将数据传递给父组件。父组件再根据传递过来的数据判断需要返回给子组件的样式结构,在template标签中使用slot-scope=子组件属性对象获取子组件数据。

// 父组件
<template>
<div>
    <templat slot-scope="scope"> // scope对象 = {value:100,index:0}
        
    </templat>
</div>
</template>

//子组件
<template>
	<slot :value="100" :index="0"></slot> // slot的所有属性都会自动传递给父组件
</template>

Vuex

利用Vuex这个插件我们也可以实现组件间通信。当Vuex注册为全局组件时,在任一组件里我们都可以用this.$store这个变量来从Vuex中取得数据并且能够操作数据。Vuex相当于充当了一个传话筒的角色,让我们能够在各个组件里自由通信。

v-model

通常v-model是使用在html中的表单标签里用于收集数据的。在html标签上的v-model本质上是用:value单向绑定一个数据,然后再用input事件触发改变绑定在:value上的值来实现的。

<input type='text' v-model='msg' />
// 等价于
<input type='text' :value='msg' @input='msg = $event.target.value' />

当我们在组件标签上使用v-model时,本质上也是先用:value单向绑定一个值,然后再用自定义的input事件监听(将子组件分发数据保存父组件的属性上)。

<myInput v-model='msg'></myInput>
// 等价于
<myInput :value='msg' @input='msg = $event.target.value'></myInput>
--------------
// myInput内部
<template>
	<div>
        <h2>input包装</h2>
        <input type='text' @input='$emit("input",$event.target.value)' />
    </div>
</template>

若组件标签里没有主动设置去使用$emit触发自定义事件则v-model并不会生效。

组件标签使用v-model本质上还是自定义事件和props的组合,它实现了父子组件双向数据同步的问题

Sync属性

sync属性修饰符也是用来实现父子组件双向数据同步的问题和v-model实现的效果几乎一样。v-model一般用于带表单项的组件而sync一般用于不带表单项的组件。使用时它们并没有严格的界限,只是我们约定成俗的地会让带有表单项的组件使用v-model,而普通组件则使用sync属性修饰符。

// 父组件
<h2>不使用sync修改符</h2>
<Child :money="total" @update:money="total=$event"/> // 事件命名格式一定要为update:传过去的数据名称
// 子组件 Child
<button @click='$emit("update:money",100)'></button>

------
// 父组件
<h2>使用sync修改符</h2>
<Child :money.sync="total"/> // 其实就是不使用sync时的语法糖

$attrs$lintener

当我们需要封装一些组件时,我们可以利用$attrs$lintener达到组件复用最大化。其实$attrs$lintener就是一个对象,$attrs可以接收父组件传递过来的全部属性,而$lintener可以接收父组件传递过来的全部事件。

// 父组件
<HintButton title="双击添加用户" type="primary" icon="el-icon-plus" @dblclick.native="add2"/>

// 子组件 HintButton
<el-button v-bind="$attrs" v-on="$listeners"></el-button> // 必须用全写v-bind和v-on
-----
// $attrs和$linstener的结构
$attrs = {
	title:"双击添加用户",
	type="primary",
	icon="el-icon-plus"
}

$linstener = {
	@dblclick.native="add2"
}

当我们需要单独去除某个父组件传递过来的属性值时,可以用props去单独接收。用props接收了的属性不再会出现在$attrs对象中

$Parent$Children$refs

$children:所有子组件对象的数组,因为它返回的是一个数组所以可以使用数组方法对其遍历,但不能用数组下标去访问某个子对象

$parent:代表父组件对象

$refs:ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素; 如果用在子组件上,引用就指向组件实例 当 v-for 用于元素或组件的时候,引用信息将是包含 DOM 节点或组件实实例的数组

  • 给子组件标签使用ref标识 <Son ref="son" /> $refs可以直接操作子组件内部的数据及方法通过this.$refs.son可以拿到组件对象本身 如果需要修改data数据 可以直接修改this.$refs.son.msg=XXX

  • 给html标签使用ref <p ref='pp'></p> 拿到的是html标签本身的dom元素this.$refs.pp

一般慎用以下方法:

找子组件时$children 是将子组件对象放入数组中不能通过索引操作因为位置不固定,$children访问子组件顺序是随机的,所以无法使用下标索引操作

找父组件时$parent存在组件共用,此时可能不是一个父组件 会存在多个父组件

父组件当中可以通过$children找到所有的子组件去操作子组件的数据(当然可以找孙子组件)

子组件当中可以通过$parent找到父组件(当然可以继续找爷爷组件)操作父组件的数据

Mixin混入技术

html、js、css相同时我们会封装组件。单个组件里js代码重复我们会封装函数。当不同的组件js代码重复 封装混合时,我们就可以使用minx混入技术,重用js代码。新建一个myminxi.js文件 在js文件中暴露一个对象 对象内部可以有data methods computed... 会将js文件中暴露出的数据 方法等混入到组件内部。

使用

//在组件内部引入 import myminxi from './myminxi.js'
//使用mixins:[mymixin]  例如:
import {mixin} from './mymixin'
export default {
    name: 'Daughter',
    mixins:[mixin],
    data(){
        money:1000
    }
},
-------
// myminx.js
export const mixin = {
  methods: {
    borrowMoney (count) {
      this.money -= count
    },
    gaveMoney (count) {
      this.money -= count
      // 给父组件增加count
      this.$parent.money += count
    }
  }
}