文章目录
组件是Vue.js最强大的功能之一,而组件实例的作用域
是相互独立的,这意味着不同组件之间的数据无法相互进行直接的引用,所以组件间的相互通信是非常重要,面试题关于组件之间通信也挺多
1.父子传参
第一种:props、$emit
// 父级给子级传
<子级组件名 :value="value"></子级组件名>
// 子级接收
props: ['value']; // 这里以字符串数组形式列出接收到的参数
// 使用watch进行监听
watch: {
// value为基本数据类型
value: {
handler(newVal, oldVal) {
// 执行的操作
}
}
// value为数组时
value: {
immediate: true,
handler(newVal) {
// 执行的操作
}
}
// value为对象时
value: { // 如果监听value对象中某个属性的变化,需加上引号 'value.name'
deep: true,
immediate: true,
handler(newVal) {
// 执行的操作
}
}
}
$emit:
// 子传出
this.$emit('submit(事件名)', value);
// 父接收
<之组件名 @submit></子组件名>
methods: {
submit(val) {
console.log(val);
}
}
第二种: ref/$refs
ref:用在子组件上,通过实例来访问组建的数据和方法
// 子组件中
data() {
return {
name: "JS"
}
},
methods: {
sayHello() {
console.log('Hello');
}
}
// 父组件
<template>
<child ref="child"></child>
</template>
<script>
import child from '@/component/child.vue'
export default {
components: { child },
mounted() {
console.log(this.$refs.child.name); // JS
this.$refs.child.sayHello(); // hello
}
}
</script>
2.兄弟组件之间传值
第一种:eventBus事件总线( e m i t / emit/ emit/on)
适用于父子组件、非父子组件等之间的通信
- 创建事件中心管理组件之间的通信
import Vue from 'vue'
export const EventBus = new Vue();
- 发送事件,假设有两个兄弟组件mouYi和mouEr
// 父组件
import Vue from 'vue';
export const EventBus = new Vue();
发送事件的两个组件:
<template>
<div>
<mou-Yi></mou-Yi>
<mou-Er></mou-Er>
</div>
</template>
<script>
import mouYi from './firstCom.vue'
import mouEr from './secondCom.vue'
export default {
components: { mouYi, mouEr }
}
</script>
在mouYi发送事件:
<template>
<div>
<button @click="add">加法</button>
</div>
</template>
<script>
import {EventBus} from './event-bus.js' // 引入事件中心
export default {
data(){
return{
num:0
}
},
methods:{
add(){
EventBus.$emit('addition', {
num:this.num++
})
}
}
}
</script>
在mouEr接收事件:
<template>
<div>求和: {{count}}</div>
</template>
<script>
import { EventBus } from './event-bus.js'
export default {
data() {
return {
count: 0
}
},
mounted() {
EventBus.$on('addition', param => {
this.count = this.count + param.num;
})
}
}
</script>
利用eventBut作为一个桥梁,其他组价可以直接访问,如果需要后期维护,比较困难。
props接收的方式和prop的验证
推荐阅读 传送 了解prop的使用
sync修饰符的使用
具体使用 传送 sync修饰符的使用以及原理
而使用.sync的区别在于
// 第一:修改 “引入子组件" 的地方
<child :isShow.sync="改变的值(要传递的值)"></child>
// 其实是方法isShow的缩写
@isShow ='(res) => isShow = res'
// 第二:修改this.$emit
props: ['isShow'];
this.$emit('update: isShow', this.show)
// update是固定的,isShow是我们要修改的状态值,与传入的状态名字相对应
3.eventBus事件总线 ($emit / $on)
eventBus事件总线适用于父子组件、非父子组件等之间的通信,类似于Vuex:
- 创建事件中心管理组件之间的通信
// eventBus.js
import Vue from 'vue';
export const EventBus = new Vue();
- 发送/接收事件 假设有两个兄弟组件firstCom和secondCom
他俩的父组件:
<template>
<div>
<first-com></first-com>
<second-com></second-com>
</div>
</template>
<script>
import firstCom from './相应路径'
import secondCom from './相应路径'
export default {
components: { firstCom, secondCom }
}
</script>
在 firstCom 组件中发送事件:
<template>
<div>
<button @click="add">点我相加</button>
</div>
</template>
<script>
import { EventBus } from '../相应路径'; // 引入事件中心
export default {
data() {
return {
num: 0
}
},
methods: {
add() {
EventBus.$emit('addFunc', {
num: this.num++
})
}
}
}
</script>
在 secondCom 组件中接收事件:
<template>
<div>总数:{{ count }}</div>
</template>
<script>
import { EventBus } from '../相应路径';
export default {
data() {
return {
count: 0
}
},
methods: {
EventBus.$on('addFunc', params => {
this.count += params.num;
})
}
}
</script>
这么一看,只是将num存储于事件总线中,而事件总线扮演的是桥梁,只是建立沟通。但是如果项目过大,有多个传递,后期维护起来还是比较难受。
4.依赖注入(provide / inject)
用于父子、祖孙组件之间的通信,好处在于 不用一层一层的传递数据
provide
和inject
是Vue提供的两个钩子,和data、methods是同级(意思是data、methods在Vue里怎么写,这两个就怎么写)的,并且 provide 的书写形式和data一样
- provide 钩子用来发送数据或方法
- inject 钩子用来接收数据或方法
用法:
父组件中:
provide() {
return {
num: this.num
}
}
子组件:
inject: ['num'];
// 还有另外一种写法,可以访问父组件中的所有属性
provide() {
return {
app: this
};
}
data() {
return {
num: 1
};
}
inject: ['app'];
console.log(this.app.num);
**注意:**依赖注入 所提供的的属性是 非响应式的(非响应式属性的值发生改变不会触发视图更新);
5.$parent / $children
- 使用 $parent 可以让组件访问父组件的实例(访问的是上一级父组件的属性和方法)
- 使用 c h i l d r e n 可以让组件访问子组件的实例,但是, children 可以让组件访问子组件的实例,但是, children可以让组件访问子组件的实例,但是,children并不能保证顺序,并且访问的数据也不是响应式的
用法:
子组件中:
<template>
<div>
<span>{{ message }}</span>
<p>获取父组件的值为:{{ parentVal }}</p>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Vue'
}
},
computed: {
parentVal() {
return this.$parent.msg
}
}
}
</script>
父组件中:
<template>
<div>
<div>{{ msg }}</div>
<child></child>
<button @click="change">点击改变子组件的值</button>
</div>
</template>
<script>
import child from './相应路径';
export default {
components: { child },
data() {
return {
msg: 'Welcome'
}
},
methods: {
// 获取子组件
change() {
this.$children[0].message = 'js'
}
}
}
</script>
子组件获取父组件中的 parentVal
值,父组件改变了子组件中的 message
的值(上面代码中,其他都比较普通,只要是两个 this.$parent
和this.$children
)
注意:
- 通过 $parent 访问到的是上一级父组件的实例,可以使用 $root 来访问根组件的实例
- 在组件中使用 $children 得到的是所有的子组件的实例,它是一个数组并且是无序的
- 在根组件 #app 上使用 $parent 得到的是 new Vue() 的实例,在这实例上再使用 $parent 得到的是 undefined ,而在最底层的子组件中使用 $children 是个空数组
- $children 的值是数组,而 $parent 是个对象
6.$attrs / $listeners
场景:A是B组件的父组件,B是C的父组件;A(爷) > B(父) > C(孙),隔代传递数据用什么好
方案:
- 用props / $emit 来一级一级的传递,能够完成,但是比较复杂
- 事件总线,但是在多人开发或者项目较大的时候,维护起来不是很方便
- Vuex,如果仅仅是数据传递,浪费资源
- 最好的办法: $attrs / $listeners 实现组件之间的跨代通信
$attrs: 继承所有的父组件属性(除了props传递的属性、class和style),一般用在子组件的子元素上;
l i s t e n e r s : 该属性是一个对象,里面包含了作用在这个组件上的所有监听器,可以配合 ‘ v − o n = " listeners: 该属性是一个对象,里面包含了作用在这个组件上的所有监听器,可以配合`v-on=" listeners:该属性是一个对象,里面包含了作用在这个组件上的所有监听器,可以配合‘v−on="listeners"` 将所有的事件监听器指向这个组件的某个特定的子元素(相当于子组件继承父组件的事件)。
inheritAttrs 属性:
- 默认值为true,继承所有的父组件属性除 props 之外的所有属性
- 只继承class属性
$attrs / $listeners 的用法
A组件(APP.vue):
<template>
<div id="app">
// 这里监听两个事件,可以在B组件或者C组件中直接触发
<child
:p-child1="child1"
:p-child2="child2"
@test1="onTest1"
@test2="onTest2"
></child>
</div>
</template>
<script>
import child1 from './相应路径';
export default {
components: { child1 },
methods: {
onTest1() {
console.log('test1 running');
},
onTest2() {
console.log('test2 running');
}
}
}
</script>
B组件(即 child1.vue ):
<template>
<div class="child-1">
<p>props: {{ pChild1 }}</p>
<p>$attrs: {{ $attrs }}</p>
<child2
v-bind="$attrs"
v-on="$listeners"
></child2>
</div>
</template>
<script>
import Child2 from './相应路径';
export default {
props: ['pChild1'],
components: { Child2 },
inheritAttrs: false, // 不继承class属性
mounted() {
this.$emit('test1'); // 触发APP.vue中的test1方法
}
}
</script>
C组件(即 child.vue ):
<template>
<div class="child2">
<p>props: {{ pChild2 }}</p>
<p>$attrs: {{ $attrs }}</p>
</div>
</template>
<script>
export default {
props: ['pChild2'],
inheritAttrs: false,
mounted() {
this.$emit('test2'); // 触发APP.vue中的test2方法
}
}
</script>
- C组件中能直接触发 test 的原因在于B组件调用C组件时 使用 v-on 绑定了 $listence 属性;
- 在B组件中通过 v-bind 绑定 $attrs 属性,C组件可以直接获取到A组件中传递下来的props(除了B组件中props声明的)
小小总结
其实不同组件之间的通信只有三种类型(也是面试中最常见的问题):父子组件间通信、兄弟组件间通信、非父子组件间通信
- 父子组件间通信(方法)
-
子组件通过props属性来接收父组件的数据,然后父组件在子组件注册监听事件,子组件通过 emit 触发事件来向父组件发送数据;
-
通过 ref 属性给予组件设置一个名字,父组件通过 $refs 组件名获取子组件,子组件通过 $parent 获得父组件;
-
使用 provide/inject ,在父组件中通过provide 提供变量,在子组件中通过 inject 来将变量注入到组件中,这是一个不需要一层一层调用的方法;
- 兄弟组件间通信(方法)
- 使用 eventBus 的方法,本质是通过创建一个空的 Vue实例来作为信息传递的桥梁,通信的组件引入这个实例,通信的组件通过在这个实例上监听和触发事件,来实现消息的传递
- 通过 $parent / $refs 来获取兄弟组件,也可以通信
- 非父子组件间通信
- 使用 eventBus ,事件总件
- Vuex 全局的变量管理