vue组件通信
组件是vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用。一般来说,组件可以有以下几种关系:
如上图所示,A和B、B和C、B和D都是父子关系,C和D是兄弟关系,A和C是隔代关系
方法一、props/$emit
父组件向子组件传递数据是通过prop传递的,子组件传递数据给父组件是通过$emit触发事件来做到的
1、父组件向子组件传值
父组件通过 import 引入子组件,并注册,在子组件标签上添加要传递的属性,子组件通过 props 接收,接收有两种形式一是通过数组形式[‘要接收的属性’ ],二是通过对象形式{ }来接收,对象形式可以设置要传递的数据类型和默认值,而数组只是简单的接收
2、子组件向父组件传值
子组件通过$emit方法(用来触发事件,详情见官网)传递参数:
子组件:
父组件:
方法二、$emit / $on (中央事件总线)
这种方法通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。
1、定义一个新的vue实例专门用于传递数据,并导出
2、定义传递的方法名和传输内容,点击事件或钩子函数触发eventBus.emit事件
3、接收传递过来的数据
注意:enentBus是一个另一个新的Vue实例,区分两个this所代表得vue实例,如果函数写成箭头函数,可以直接使用this,箭头函数内部不会产生新的this
方法三、Vuex
1、 简要介绍 Vuex 原理
Vuex 实现了一个单向数据流,在全局拥有一个 State 存放数据,当组件要更改 State 中的数据时,必须通过 Mutation 进行,Mutation 同时提供了订阅者模式供外部插件调用获取 State 数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走 Action,但 Action 也是无法直接修改 State 的,还是需要通过 Mutation 来修改 State 的数据。最后,根据 State 的变化,渲染到视图上。
主要流程:组件通过 dispatch 到 actions,actions 是异步操作,再 actions中通过 commit 到 mutations,mutations 再通过逻辑操作改变 state,从而同步到组件,更新其数据状态
方法四、provide / inject
provide / inject 是 2.2 新增的方法,可以以一个祖先组件向所有子孙后代注入依赖(一个内容)。
provider/inject:简单的来说就是在父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量。
以上两者可以在父组件与子组件、孙子组件、曾孙子…组件数据交互,也就是说不仅限于prop的父子组件数据交互,只要在上一层级的声明的provide,那么下一层级无论多深都能够通过inject来访问到provide的数据
1.父级组件如下
<template>
<div class="test">
<son prop="data"></son>
</div>
</template>
<script>
export default {
name: 'Test',
provide: {
name: 'Garrett'
}
}
</script>
2.孙子组件,注意这里是孙子组件,父级 -> 子组件 -> 孙子组件三个层级关系
<template>
<div>
{{name}}
</div>
</template>
<script>
export default {
name: 'Grandson',
inject: [name]
}
</script>
这里可以通过inject直接访问其两个层级上的数据,其用法与props完全相同,同样可以参数校验等
方法五、$attrs / $listeners
多级组件嵌套需要传递数据时,通常使用的方法是通过 vuex。但如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,未免有点大材小用。为此 Vue2.4 版本提供了另一种方法----$attrs / $listeners
$ attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 interitAttrs 选项一起使用。
(简言之:接收除了props声明外的所有绑定属性(class、style除外))
//parent组件
<template>
<div>
<Son :name='name' :age='age'/>
</div>
</template>
<script>
export default {
data() {
return {
name: 'Eric',
age: '20'
}
}
}
</script>
//Son组件
<template>
<div>
//此时Son组件把从父组件传来的数据,在传给孙组件
<GrandSon v-bind='$attrs'/>
</div>
</template>
<script>
export default {
props: ['name'],
mounted() {
//父组件共传来连个值,一个name, 一个age,由于name被props接收了,故$attrs 只有age属性
console.log(this.$attrs) //{age: '20'}
}
}
</script>
//GrandSon组件
<template>
<div>
这是孙组件
</div>
</template>
<script>
export default {
mounted() {
//这样父祖组件就把数据传到了孙组件
console.log(this.$attrs) // {age: '20'}
}
}
</script>
$ listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件
(简言之:接收除了带有.native事件修饰符的所有事件监听器)
接下来我们看个跨级通信的例子:
//parent组件
<template>
<div>
<Son @eventOne='eventHandler' @click.native='clickHandler'/>
</div>
</template>
<script>
export default {
methods: {
eventHandler() {
console.log('触发了Parent中的eventHandler')
},
clickHandler() {
console.log('触发了Parent中的clickHandler')
}
}
}
</script>
//Son组件
<template>
<div>
//此时Son组件把从父组件传来的监听器,在传给孙组件
<GrandSon v-on='$listerners'/>
</div>
</template>
<script>
export default {
mounted() {
//父组件监听了两个事件,一个eventOne, 一个click,由于click被native修饰了,故$listerners 只有eventOne事件
console.log(this.$listerners) //{eventOne: fn}
this.$emit('eventOne') //可以触发Parent组件中的eventHandler函数
}
}
</script>
//GrandSon组件
<template>
<div>
这是孙组件
</div>
</template>
<script>
export default {
mounted() {
//这样孙组件通过emit触发Parent组件中的监听函数
console.log(this.$listerners) // {eventOne: fn}
this.$emit('eventOne') //可以触发Parent组件中的eventHandler函数
}
}
</script>
方法六、$parent / $children与 ref
- ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
- $parent / $children:访问父 / 子实例
需要注意的是:这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。我们先来看个用 ref来访问组件的例子:
// component-a 子组件
export default {
data () {
return {
title: 'Vue.js'
}
},
methods: {
sayHello () {
window.alert('Hello');
}
}
}
// 父组件
<template>
<component-a ref="comA"></component-a>
</template>
<script>
export default {
mounted () {
const comA = this.$refs.comA;
console.log(comA.title); // Vue.js
comA.sayHello(); // 弹窗
}
}
</script>
不过,这两种方法的弊端是,无法在跨级或兄弟间通信。
总结
常见使用场景可以分为三类:
- 父子通信:
父向子传递数据是通过 props,子向父是通过 events($ emit);通过父链 / 子链也可以通信($ parent / $ children);ref 也可以访问组件实例;provide / inject API;$ attrs/$listeners - 兄弟通信:
Bus;Vuex - 跨级通信:
Bus;Vuex;provide / inject API、$ attrs/$listeners