VUE2.0组件间通信
Vue组件通信的方式,这是在面试中一个非常高频的问题,很多人只知道props和 $emit,下面简单介绍一下Vue的一些其他传值方式。
一、props/$emit
props/$emit可以说Vue里面最常见的通信方式。
props
: props用于接收来自父组件通过v-bind传递的数据,可以通过type、default、required、validator等配置来设置属性的类型、默认值、是否必传和校验规则。
$emit
: 在组件通信时,我们通常使用 $emit来触发父组件v-on在子组件上绑定相应事件的监听。
`自定义 v-model, 父组件 通过 v-bind: value, 子组件通过 this.$emit('input', newValue) 和 this.$emit('change', newValue)`
代码示例
父向子传值:父组件通过 :type="type
" :size="size
" 将父组件type/size 值传递给子组件。
子向父传值:父组件通过@click="handleClick
"在子组件上绑定了 click 事件的监听,子组件 my-button标签点击时,会触发 click 回调函数, 通过this.$emit(click', message)
将子组件 message 的值赋值给父组件。
<template>
<!-- 这是子组件 -->
<button
class="my-button"
:class="['my-button-size-'+size, 'my-button-'+type, ]"
@click="click">
<slot></slot>
</button>
</template>
<script>
export default {
name: 'MyButton',
props: {
// size 属性是用来控制按钮的大小样式,通过不同 size 设置不同的类名
size: {
type: String,
default: 'big'
},
// size 属性是用来控制按钮的颜色样式,type来控制主题、颜色
type: {
type: String,
default: 'default'
}
},
methods: {
click() {
this.$emit('click', `子组件点击了一下按钮`)
}
}
}
</script>
<template>
<!-- 这是父组件 -->
<div>
<my-button
:type="type"
:size="size"
@click="handleClick">按钮</my-button>
<p>子组件:{{ childCallbackWord }}</p>
</div>
</template>
<script>
export default {
name: 'parent-component',
data () {
return {
type: 'primary',
size: 'small',
childCallbackWord: '' // 子组件点击事件回调参数
}
},
methods: {
handleClick (msg) {
// 接受子组件的信息,并将其赋值给childCallbackWord
this.childCallbackWord = msg
}
}
}
</script>
二、v-slot
v-slot
是 Vue2.6 版本中新增的用于统一实现插槽和具名插槽的api。
v-slot
在 template 标签中用于提供具名插槽或需要接收 prop 的插槽,如果不指定 v-slot ,则取默认值 default 。
代码示例
父向子传值:父组件通过默认slot 将父组件的message值传递给子组件,子组件通过<slot></slot>
接收到相应内容,实现了父向子传值。
<!-- 子组件参照props/$emit中示例的子组件 -->
<template>
<!-- 这是父组件 -->
<div class="parent">
<p><input type="text" v-model="message"></p>
<my-button :type="type" size="small">{{message}}</my-button>
</div>
</template>
<script>
export default {
name: 'example-button',
data () {
return {
type: 'primary',
message: '' // 插槽message
}
}
}
</script>
三、$refs/ $parent/ $children/ $root
我们也同样可以通过 $refs/$parent/$children/$root
等方式获取 Vue 组件实例,得到实例上绑定的属性及方法等,来实现组件之间的通信。
$refs
: 我们通常会将 $refs绑定在DOM元素上,来获取DOM元素的 attributes。在实现组件通信上,我们也可以将 $refs 绑定在子组件上,从而获取子组件实例。
$parent
: 我们可以在 Vue 中直接通过this.$parent
来获取当前组件的父组件实例(如果有的话)。
$children
: 同理,我们也可以在 Vue 中直接通过this.$children
来获取当前组件的子组件实例的数组。但是需要注意的是,this.$children数组中的元素下标并不一定对用父组件引用的子组件的顺序,例如有异步加载的子组件,可能影响其在 children 数组中的顺序。所以使用时需要根据一定的条件例如子组件的name去找到相应的子组件。
$root
: 获取当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己。通过 $root ,我们可以实现组件之间的跨级通信。
代码示例
以上四个都是获取对应的示例,用法基本相同,以下以$parent
$children
为例。
父向子传值:子组件通过$parent.message获取到父组件中message的值。
子向父传值:父组件通过 $children
获取子组件实例的数组,遍历通过 child。$options.name
获取对应name的child实例。再通过child实例 child.message获取到子组件中message的值。
<template>
<!-- 这是子组件 -->
<p>收到来自父组件的消息:{{ $parent.message }}</p>
</template>
<script>
export default {
name: 'my-button',
data () {
return {
message: '我是子组件中的message'
}
}
}
</script>
<template>
<!-- 这是父组件 -->
<div class="parent">
<my-button></my-button>
<p>子组件中message:{{ child.message }}</p>
</div>
</template>
<script>
export default {
name: 'parent',
data () {
return {
child: {},
message: '我是父组件中的message'
}
},
mounted () {
this.child = this.$children.find((child) => {
// 通过options.name获取对应name的child实例
return child.$options.name === 'my-button'
})
}
}
</script>
四、 $attrs / $listener
$attrs
: 用来接收父作用域中不作为 prop 被识别的 attribute 属性,并且可以通过v-bind="$attrs"传入内部组件——在创建高级别的组件时非常有用。
$listeners
: 包含了父作用域中的 v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用,这里在传递时的使用方法和 $attrs 十分类似。
// 3级组件C
<template>
<div class="compc">
<!--将A组件keyup的监听回调绑在该input上-->
<input
name="compC"
type="text"
v-model="message"
v-on="$listeners" />
<p>收到来自A组件的消息:{{ $attrs.messageFromA }}</p>
</div>
</template>
<script>
export default {
name: 'Compc',
data() {
return {
message: '',
}
},
}
</script>
// 2级组件B
<template>
<div class="compb">
<h4>this is B component</h4>
<!--将A组件keyup的监听回调绑在该input上-->
<input
name="compB"
type="text"
v-model="message"
v-on="$listeners" />
<p>收到来自A组件的消息:{{ $attrs.messageFromA }}</p>
<!--将A组件keyup的监听回调、A组件传递的attrs继续传递给C组件-->
<CompC v-bind="$attrs" v-on="$listeners" />
</div>
</template>
<script>
import CompC from './compC'
export default {
name: 'CompB',
components: {
CompC,
},
data() {
return {
message: '',
}
},
}
</script>
// A组件
<template>
<div class="compa">
<h3>this is A component</h3>
<input type="text" v-model="message" />
<p>收到来自{{ comp }}的消息:{{ messageFromComp }}</p>
<!--监听子孙组件的keyup事件,将message传递给子孙组件-->
<CompB :messageFromA="message" @keyup="receive" />
</div>
</template>
<script>
import CompB from './compB'
export default {
name: 'CompA',
data() {
return {
message: '',
messageFromComp: '',
comp: '',
}
},
components: {
CompB,
},
methods: {
receive(e) {
// 监听子孙组件keyup事件的回调,
// 并将keyup所在input输入框的值赋值给messageFromComp
this.comp = e.target.name
this.messageFromComp = e.target.value
},
},
}
</script>
五、provide/inject
provide
inject
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深
provide: 是一个对象,或者是一个返回对象的函数。该对象包含可注入其子孙的 property ,即要传递给子孙的属性和属性值。
提示:由于vue provide 和 inject 绑定并不是可响应的,所以要用object类型而不是string/number类型
injcet: 一个字符串数组,或者是一个对象。当其为字符串数组时,使用方式和props十分相似,只不过接收的属性由data变成了provide中的属性。当其为对象时,也和props类似,可以通过配置default和from等属性来设置默认值,在子组件中使用新的命名属性等。
代码示例
// C组件
<template>
<div class="compc">
<p>收到来自A组件的消息:{{ messageFromA && messageFromA.content }}</p>
</div>
</template>
<script>
export default {
name: 'CompC',
// 通过inject接受A中provide传递过来的message
inject: ['messageFromA']
}
</script>
// B组件
<template>
<div class="compB" >
<p>收到来自A组件的消息:{{ messageFromA && messageFromA.content }}</p>
<ComC />
</div>
</template>
<script>
import ComC from './C.vue'
export default {
name: 'ComB',
components: {
ComC
},
// 通过inject接受A中provide传递过来的message
inject: ['messageFromA']
}
</script>
<template>
<div class="compA">
<h3>这是A组件</h3>
<ComB />
</div>
</template>
<script>
import ComB from './B.vue'
export default {
name: 'CompA',
components: {
ComB
},
provide () {
return {
// 将message通过provide传递给子孙组件
messageFromA: {
content: '这是A的message'
}
}
}
}
</script>
六、eventBus
eventBus又称事件总线,通过注册一个新的Vue实例,通过调用这个实例的$emit
和$on
等来监听和触发这个实例的事件,通过传入参数从而实现组件的全局通信。它是一个不具备 DOM 的组件,有的仅仅只是它实例方法而已,因此非常的轻便。
// bus.js,使用时通过import引入
import Vue from 'vue'
export const Bus = new Vue()
/*
vm.$on( event, callback )
参数:
{string | Array<string>} event (数组只在 2.2.0+ 中支持)
{Function} callback
用法:
监听当前实例上的自定义事件。事件可以由 vm.$emit 触发。回调函数会接收所有传入事件触发函数的额外参数。
vm.$once( event, callback )
参数:
{string} event
{Function} callback
用法:
监听一个自定义事件,但是只触发一次。一旦触发之后,监听器就会被移除。
vm.$off( [event, callback] )
参数:
{string | Array<string>} event (只在 2.2.2+ 支持数组)
{Function} [callback]
用法:
移除自定义事件监听器。
如果没有提供参数,则移除所有的事件监听器;
如果只提供了事件,则移除该事件所有的监听器;
如果同时提供了事件与回调,则只移除这个回调的监听器。
vm.$emit( eventName, […args] )
参数:
{string} eventName
[...args]
触发当前实例上的事件。附加参数都会传给监听器回调。
代码示例
<template>
<div>
<p>我是父组件: {{ message }}</p>
<Son></Son>
</div>
</template>
<script>
import Son from './Son.vue'
import { Bus } from '../bus.js'
export default {
data () {
return {
message: ''
}
},
components: {
Son
},
mounted () {
// 监听grandson事件
Bus.$on('grandson', (msg) => {
this.message = msg
})
},
methods: {
handleMmsg (msg) {
//移除事件监听,故Parent只监听一次,减少内存消耗
Bus.$off('grandson')
}
}
}
</script>
<template>
<div>
<p>我是子组件 : {{ message }}</p>
<Grandson></Grandson>
</div>
</template>
<script>
import Grandson from './Grandson.vue'
import { Bus } from '../bus.js'
export default {
name: 'my-button',
components: {
Grandson
},
data () {
return {
message: ''
}
},
mounted () {
// 监听事件
Bus.$on('grandson', (msg) => {
this.message = msg
})
}
}
</script>
<template>
<div>
<p>我是孙子组件 </p>
<button @click="handleMsg">点击发出消息</button>
</div>
</template>
<script>
import { Bus } from '../bus.js'
export default {
methods: {
handleMsg () {
Bus.$emit('grandson', '我是组件GrandSon')
}
}
}
</script>
实现了全局通信:即父子组件相互通信、兄弟组件相互通信、跨级组件相互通信。孙组件Bus.$emit('grandson', message)
将message作为参数传入;同时通过this.$bus.$on('grandson', message)
监听其他组件的grandson事件,获取实例当前组件示例message。这样其他组件都能接收到相应的信息,实现全局通信。
七、Vuex
Vuex
是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
具体参照 Vuex官方文档[1]
代码示例
具体的代码就不放出来了…
总结
以上提及到7总组件间通信的方式,可能没有涉及全,但希望能帮助到你们!
props/$emit
:可以实现父子组件的双向通信,在日常的父子组件通信中一般会作为我们的最常用选择。
v-slot
:可以实现父子组件单向通信(父向子传值),在实现可复用组件,向组件中传入DOM节点、html等内容以及某些组件库的表格值二次处理等情况时,可以优先考虑v-slot。
$refs/$parent/$children/$root
:可以实现父子组件双向通信,其中 $root可以实现根组件实例向子孙组件跨级单向传值。在父组件没有传递值或通过v-on绑定监听时,父子间想要获取彼此的属性或方法可以考虑使用这些api。
$attrs/$listeners
:能够实现跨级双向通信,能够让你简单的获取传入的属性和绑定的监听,并且方便地向下级子组件传递,在构建高级组件时十分好用。
provide/inject
:可以实现跨级单向通信,轻量地向子孙组件注入依赖,这是你在实现高级组件、创建组件库时的不二之选。
eventBus
:可以实现全局通信,在项目规模不大的情况下,可以利用eventBus实现全局的事件监听。但是eventBus要慎用,避免全局污染和内存泄漏等情况。
Vuex
:可以实现全局通信,是vue项目全局状态管理的最佳实践。在项目比较庞大,想要集中式管理全局组件状态时,那么安装Vuex准没错!
参考资料
[1] Vuex官方文档: https://vuex.vuejs.org/zh/