Vue组件之间的通信方式
前言
组件是 vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用。而很多情况下我们的组件之间是需要通信的,可以使用不同的方法(props/$emit,$emit/$on、vuex、$parent / $children/$ref、$attrs/$listeners和provide/inject
)在不同关系(父子组件,如:A.Vue和B.Vue,隔代组件,如A.Vue和C.Vue,兄弟组件,如C.Vue和D.Vue)的组件之间进行通信。
一、props/$emit
父子组件的关系可以总结为 prop 向下传递,事件向上传递。父组件通过 prop 给子组件下发数据,子组件通过事件给父组件发送消息,如下图所示:
props:父组件向子组件传值
子组件在props中定义自己需要接收的参数,父组件通过v-bind:prop-name将参数传递给子组件
childB.Vue
<template>
<div>
<p>{{ title }}</p>
</div>
</template>
<script>
export default {
name: 'child-B',
props: {
title: { // 子组件B中定义title属性,接收父组件的传值
type: String,
default: ''
}
},
data () {
return {
}
}
}
</script>
A.Vue
<template>
<childB :title="childBTitle"></childB> <!-- 父组件将childBTitle与组件B的title属性进行绑定 -->
</template>
<script>
import childB from '@/components/child_B'
export default {
components: {
childB
},
data () {
return {
childBTitle: 'from A to B'
}
}
}
</script>
二、$emit
:父组件监听子组件的自定义事件
子组件可以通过提交自定义事件的方式,父组件监听该自定义事件,从而更新父组件的值
childB.Vue
<template>
<div>
<p>{{ title }}</p>
<bk-button @click="changeATitle">ChildB_Button</bk-button>
</div>
</template>
<script>
export default {
name: 'child-B',
components: {
},
props: {
title: {
type: String,
default: ''
}
},
data () {
return {
}
},
methods: {
changeATitle () { // 自定义事件titleChanged,传递值“子向父组件传值”
this.$emit('titleChanged', '子向父组件传值')
}
}
}
</script>
A.Vue
<template>
<div>
<childB :title="childBTitle" @titleChanged="updateTitle"></childB> // 监听titleChanged方法
<h1>{{ title }}</h1>
</div>
</template>
<script>
import childB from '@/components/child_B'
export default {
components: {
childB
},
data () {
return {
childBTitle: 'from A to B',
title: ''
}
},
methods: {
updateTitle (e) { // e接收子组件传递的参数
this.title = e
}
}
}
</script>
双向绑定.sync
.sync修饰符是Vue 2.3.0+ 新增的,可用于父子组件之间值的“双向绑定”(以 update:myPropName 的模式触发事件代替真正的双向绑定),其实该方法就是props绑定父组件的值,
e
m
i
t
更新父组件值的简洁写法。子组件:
‘
t
h
i
s
.
emit更新父组件值的简洁写法。 子组件:`this.
emit更新父组件值的简洁写法。子组件:‘this.emit(‘update:title’, newTitle)`
父组件可以监听update方法
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"></text-document>
然后使用.sync代替,可写成:
<text-document :title.sync="doc.title"></text-document>
另外,如果绑定的是一个Object,可以写成:
<text-document v-bind.sync="doc"></text-document>
这样会把 doc 对象中的每一个 property (如 title) 都作为一个独立的 prop 传进去,然后各自添加用于更新的 v-on 监听器。
B.Vue
<template>
<div>
<bk-button @click="changeATitle">ChildB_Button</bk-button>
<p>ChildB:{{ name }}-{{ age }}-{{ weight }}</p>
</div>
</template>
<script>
export default {
name: 'child-B',
components: {
},
props: {
name: {
type: String,
default: 'Bob'
},
age: {
type: Number,
default: 11
},
weight: {
type: String,
default: '50kg'
}
},
data () {
return {
}
},
methods: {
changeATitle () {
this.$emit('update:name', 'Jack') // 自定义事件,传递值“Jack”
}
}
}
</script>
A.Vue
<template>
<div>
<childB v-bind.sync="doc"></childB>
<h1>ParentATitle:{{ doc.name }}-{{ doc.age }}-{{ doc.weight }}</h1>
</div>
</template>
<script>
import childB from '@/components/child_B'
export default {
components: {
childB
},
data () {
return {
doc: {
name: 'Bob',
age: 11,
weight: '50kg'
}
}
}
}
</script>
$emit/$on
通过创建一个空的实例作为中央事件总线,我们可以借由它来监听和触发事件,这样就实现了任何组件之间的通信。
主要由以下三部分组成:
中央事件总线:var Event=new Vue()
事件触发组件:Event.$emit(事件名,数据)
事件监听组件:Event.$on(事件名,data => {})
创建中央事件总线
创建bus.js文件
import Vue from 'vue'
export const bus = new Vue() // 创建Vue实例并暴露出去
触发事件
A.Vue
<template>
<div>
<childB></childB>
<bk-button @click="changeTitle">触发Bus事件</bk-button>
</div>
</template>
<script>
import { bus } from '@/common/bus'
import childB from '@/components/child_B'
export default {
components: {
childB
},
data () {
return {
doc: {
name: 'Bob',
age: 11,
weight: '50kg'
}
}
},
methods: {
changeTitle () {
bus.$emit('updateTitle', 'Jack') // 触发事件更新B的title
}
}
}
</script>
监听事件
B.Vue
<template>
<div>
<p>{{ title }}</p>
</div>
</template>
<script>
import { bus } from '@/common/bus'
export default {
name: 'child-B',
components: {
},
props: {
},
data () {
return {
title: 'test'
}
},
created () {
bus.$on('updateTitle', title => { // 监听事件,更新title
this.title = title
})
}
}
</script>
使用vue-happy-bus
为了使bus事件的自动销毁,我们可以引用vue-happy-bus插件
main.js
import BusFactory from 'vue-happy-bus'
Vue.prototype.$busFactory = BusFactory
A.Vue
data () {
return {
// bus实例
Bus: this.$busFactory(this)
},
methods: {
changeTitle () {
this.Bus.$emit('updateTitle', 'Jack') // 触发事件更新B的title
}
}
vuex
vuex介绍
vuex 是 vue 的状态管理器,存储的数据是响应式的。它由以下几个部分组成:
1)Vue Components:Vue组件。
2)dispatch:操作行为触发方法,是唯一能执行action的方法。
3)actions:操作行为处理模块,由组件中的$store.dispatch(‘action 名称’, data1)来触发。然后由commit()来触发mutation的调用 , 间接更新 state。
4)commit:状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法。
5)mutations:状态改变操作方法,由actions中的commit(‘mutation 名称’)来触发。
6)state:页面状态管理容器对象。集中存储Vue components中data对象的零散数据,全局唯一,以进行统一的状态管理。
7)getters:state对象读取方法。
State用于保存数据,全局唯一,它可用于Vue组件的渲染,它的改变只能由Mutations来引起。组件可以通过调用dispatch方法,来触发Actions,Actions再把相应的改变提交到Mutations,从而引起State改变。
Vuex的使用
首先在src目录下创建如下目录,modules文件夹内可放置各个不同视图的一些全局参数,下面示例放置了一个侧边导航栏的收缩按钮参数。
B.Vue
子组件通过Getter获取属性值且可以通过dispatch触发Actions,再通过commit方法提交变化,从而引起全局参数的状态变化。
$parent/$children/$ref
ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
$parent / $children:访问父 / 子实例
需要注意的是:这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。但这种方式无法进行兄弟组件间的通信。
B.Vue
<template>
<div>
<p>{{ title }}</p>
</div>
</template>
<script>
export default {
name: 'child-B',
components: {
},
props: {
},
data () {
return {
title: 'test'
}
}
}
</script>
A.Vue
<template>
<div>
<childB></childB>
<bk-button @click="changeTitle">修改子组件的值</bk-button>
</div>
</template>
<script>
import childB from '@/components/child_B'
export default {
components: {
childB
},
data () {
return {
}
},
methods: {
changeTitle () {
this.$children[0].title = 'ChangeBTitle'
}
}
}
</script>
$attrs/$listeners
对于嵌套的组件,即隔代组件之间的通信,我们可以使用 a t t r s / attrs/ attrs/listeners来进行属性的修改或者事件的监听。
- a t t r s :包含了父作用域中不被 p r o p 所识别 ( 且获取 ) 的特性绑定 ( c l a s s 和 s t y l e 除外 ) 。当一个组件没有声明任何 p r o p 时,这里会包含所有父作用域的绑定 ( c l a s s 和 s t y l e 除外 ) ,并且可以通过 v − b i n d = " attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind=" attrs:包含了父作用域中不被prop所识别(且获取)的特性绑定(class和style除外)。当一个组件没有声明任何prop时,这里会包含所有父作用域的绑定(class和style除外),并且可以通过v−bind="attrs" 传入内部组件。通常配合 interitAttrs 选项一起使用。
-
l
i
s
t
e
n
e
r
s
:包含了父作用域中的
(
不含
.
n
a
t
i
v
e
修饰器的
)
v
−
o
n
事件监听器。它可以通过
v
−
o
n
=
"
listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="
listeners:包含了父作用域中的(不含.native修饰器的)v−on事件监听器。它可以通过v−on="listeners" 传入内部组件
A.Vue
<template>
<div>
<!-- 把属性传入,后代组件可以通过$attrs获取;监听自定义方法updateATitle -->
<childB :foo="foo" :boo="boo" :coo="coo" :doo="doo" @updateATitle="updateTitle"></childB>
<h>A标题:{{ title }}</h>
</div>
</template>
<script>
import childB from '@/components/child_B'
export default {
components: {
childB
},
data () {
return {
foo: 'Javascript',
boo: 'Html',
coo: 'CSS',
doo: 'Vue',
title: 'Origin Title'
}
},
methods: {
updateTitle (e) {
this.title = e
}
}
}
</script>
B.Vue
<template>
<div>
<!-- 把$attrs传入,后代组件可以通过$attrs获取;$listeners将父组件的方法传入到后代组件 -->
<grandsonC v-bind="$attrs" v-on="$listeners"></grandsonC>
<p>B-attrs{{ $attrs }}</p>
</div>
</template>
<script>
import grandsonC from '@/components/grandson_C'
export default {
name: 'child-B',
components: {
grandsonC
},
props: {
foo: {
type: String,
default: ''
}
},
data () {
return {
title: 'test'
}
}
}
</script>
C.Vue
<template>
<div>
<p>C-Attrs{{ $attrs }}</p>
<bk-button @click="changTitle">孙子C改A标题</bk-button>
</div>
</template>
<script>
export default {
name: 'child-C',
components: {
},
inheritAttrs: false,
props: {
bizInsts: {
type: Array,
default: []
}
},
data () {
return {
}
},
computed: {
},
mounted () {
},
methods: {
changTitle () {
this.$emit('updateATitle', 'From C change') // 触发自定义事件
}
}
}
</script>
provide/inject
Vue2.2.0新增API,这对选项需要一起使用。它可以用于隔代组件之间的传值,先代组件以provide属性传入参数,后代组件可以以inject属性去获取这些参数,不论层级有多深。
provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
A.Vue
<template>
<div>
<childB></childB>
</div>
</template>
<script>
import childB from '@/components/child_B'
export default {
components: {
childB
},
provide: {
title: '祖先标题'
}
}
</script>
B.Vue
<template>
<div>
<grandsonC></grandsonC>
<p>B-Title{{ title }}</p>
</div>
</template>
<script>
import grandsonC from '@/components/grandson_C'
export default {
name: 'child-B',
inject: ['title'],
components: {
grandsonC
},
props: {
}
}
</script>
C.Vue
<template>
<div>
<p>C-Title{{ title }}</p>
<grandsonD v-bind="$attrs" v-on="$listeners"></grandsonD>
</div>
</template>
<script>
import grandsonD from '@/components/grandson_D'
export default {
name: 'child-C',
inject: ['title'],
components: {
grandsonD
}
}
</script>
D.Vue
<template>
<div>
<p>D-Title{{ title }}</p>
</div>
</template>
<script>
export default {
name: 'child-D',
inject: ['title'],
components: {
}
}
</script>
provide 和 inject 绑定并不是可响应的,即这种方式只能作为传值使用,如果要修改值的话,可以将祖先元素传入。
总结
组件同心的三种场景的适用方式如下:
父子组件通信:props/$emit
,$emit/$on(bus)
,vuex
,$parent/$children/$ref
,
a
t
t
r
s
/
attrs/
attrs/listeners, provide/inject
【6】
嵌套多层组件通信:$emit/$on(bus)
,vuex
, $attrs/$listeners
, provide/inject
【4】
兄弟组件通信:$emit/$on(bus)
,vuex
【2】