vue组件间通信常用的六种方式
组件是 vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用。一般来说,组件可以有以下几种关系:
如上图所示,A 和 B、B 和 C、B 和 D 都是父子关系,C 和 D 是兄弟关系,A 和 C 是隔代关系(可能隔多代)。
针对不同的使用场景,如何选择行之有效的通信方式?
下面就介绍几种vue组件之间通信的方式:
方法一 props/$emit
这种方式比较简单,也是最基本的传值方式。
父组件ParentView.vue中的currentTime通过props传入Children1.vue组件中显示,子组件通过触发父组件传递的方式getChildData,利用参数的形式将要传递给父组件的数据传递。
父组件代码:
<template>
<div class="parent_view">
<div class="item_view">
<h1>父组件</h1>
<div style="text-align:left;">当前时间为:</div>
<div class="time_view">{{currentTime}}</div>
</div>
<div class="item_view">
<h1>子组件1</h1>
<child1 :cTime="currentTime" @getChildData="getChildData"></child1>
</div>
</div>
</template>
<script>
import child1 from './children/Children1.vue'
export default {
name: 'parentView',
data () {
return {
currentTime: '',
}
},
components: {
child1,
},
methods: {
getChildData (data) {
this.currentTime = data;//将子组件传递的数据显示在父组件后面
},
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.parent_view {
width: 90%;
height: 750px;
margin: 0 auto;
border: 1px solid cadetblue;
border-radius: 5px;
display: flex;
justify-content: center;
align-items: center;
}
.item_view {
flex: 1;
margin: 10px 10px;
height: 95%;
border: 1px solid cyan;
border-radius: 5px;
overflow: auto;
}
.time_view {
color: cornflowerblue;
font-weight: 800;
text-align: center;
margin: 10px;
}
</style>
子组件代码:
<template>
<div class="child_1">
<p style="color:skyblue;">{{cTime}}</p>
<button @click="PushData">向父组件传递子组件新时间</button>
<p style="color:red;">{{time}}</p>
</div>
</template>
<script>
export default {
name: 'child_1',
props: {
cTime: {
type: String,
default: ''
}
},
data () {
return {
time: new Date().toLocaleString()
}
},
methods: {
PushData () {
let date = new Date().toLocaleString().replace(/\//g, '_')
this.$emit('getChildData', date)
}
}
}
</script>
<style scoped>
</style>
tips: 子组件通过events给父组件发送消息,实际上就是子组件把自己的数据发送到父组件。
方法二 $emit / $on
这种方法也是比较轻量常见的方式,通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。当我们的项目比较大时,可以选择更好的状态管理解决方案vuex。
注册全局bus
import Vue from 'vue'
const bus = new Vue()
export default bus
父组件代码:
<template>
<div class="parent_view">
<div class="item_view">
<h1>父组件</h1>
<div style="text-align:left;">当前时间为:</div>
<div class="time_view">{{currentTime}}</div>
<button @click="send">向子组件发送时间</button>
</div>
<div class="item_view">
<h1>子组件1</h1>
<child1 :cTime="currentTime" @getChildData="getChildData"></child1>
</div>
<div class="item_view">
<child2></child2>
</div>
</div>
</template>
<script>
import child1 from './children/Children1.vue'
import child2 from './children/Children2.vue'
import bus from '../utils/bus.js'
export default {
name: 'parentView',
data () {
return {
currentTime: '',
}
},
components: {
child1,
child2,
},
methods: {
getChildData (data) {
this.currentTime = data;//将子组件传递的数据显示在父组件后面
},
send () {
bus.$emit("sendChild", this.currentTime);
},
}
}
</script>
子组件代码:
<template>
<div class="child_1">
<h1>子组件2</h1>
<p>接收来自父组件的数据</p>
<p style="color:red;">{{time}}</p>
<childItem1></childItem1>
</div>
</template>
<script>
import bus from '../../utils/bus.js'
import childItem1 from './items/ChildrenItems.vue'
export default {
name: 'child_2',
data () {
return {
time: ''
}
},
components: {
childItem1
},
methods: {
},
mounted () {
bus.$on("sendChild", data => {
this.time = data;
});
}
}
</script>
<style scoped>
</style>
子组件的子组件代码:
<template>
<div class="child_1">
<h3>子组件2_2</h3>
<p>接收来自父组件的数据</p>
<p style="color:cyan;">{{time}}</p>
</div>
</template>
<script>
import bus from '../../../utils/bus.js'
export default {
name: 'child_2',
data () {
return {
time: ''
}
},
methods: {
},
mounted () {
bus.$on("sendChild", data => {
this.time = data;
});
}
}
</script>
<style scoped>
</style>
方法三:v-modal
v-modal实现的是仅限父子组件的数据双向绑定。
父组件代码:
<template>
<div class="parent_view">
<div class="item_view">
<h1>父组件</h1>
<p style="text-align:left;">message</p>
<p style="text-align:left;color:red;">{{massage}}</p>
</div>
<div class="item_view">
<child3 v-model="massage"></child3>
</div>
</div>
</template>
<script>
import child3 from './children/Children3.vue'
export default {
name: 'parentView',
data () {
return {
currentTime: '',
massage: '这是简单的组件传值的例子!',
}
},
components: {
child3,
},
methods: {
}
}
</script>
子组件代码:
<template>
<div class="child_1">
<h1>子组件3</h1>
<p>接收来自父组件的message</p>
<p style="color:green;">{{pMessage}}</p>
<button @click="change">改变modal的值</button>
</div>
</template>
<script>
export default {
name: 'child_3',
data () {
return {
}
},
model: {
prop: 'pMessage',
event: 'setValue'
},
props: {
pMessage: {
type: String,
default: ''
}
},
methods: {
change () {
this.$emit('setValue', '哇咔咔~~~~')
}
},
}
</script>
<style scoped>
</style>
方法四:$parent / $children / $refs
ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
$parent / $children:访问父 / 子实例
需要注意的是:这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。
不过,这两种方法的弊端是,无法在跨级或兄弟间通信。
如果子组件是公共组件,会被多个父组件调用,那么$parent会怎么获取?改变他们的属性将会怎么变化?父组件中没有这个属性怎么办?
针对不同父组件调用,子组件会每次都会生成一个实例,这也是Vue的重要机制。parent会获取每个调用它的父组件实例。子组件中通过parent会获取每个调用它的父组件实例。子组件中通过parent会改变每个调用它的父组件中的对应属性。
父组件代码:
<template>
<div class="parent_view">
<div class="item_view">
<h1>父组件</h1>
<p style="text-align:left;">sangName</p>
<p style="text-align:left;color:plum;">{{sangName}}</p>
</div>
<div class="item_view">
<child4 :sangName="sangName"></child4>
</div>
</div>
</template>
<script>
import child4 from './children/Children4.vue'
export default {
name: 'parentView',
data () {
return {
sangName: '现在听的歌叫《Please Don’t Touch》',
}
},
components: {
child4,
},
methods: {
changeSang (data) {
this.sangName += data
},
sendSang () {
this.$children[0].childChange(this.$children[0].childData)
}
}
}
</script>
子组件代码:
<template>
<div class="child_1">
<h1>子组件4</h1>
<p>接收来自父组件的sangName</p>
<p>{{$attrs.sangName}}</p>
<button @click="sendSang">改变sangName的值</button>
</div>
</template>
<script>
export default {
name: 'child_4',
data () {
return {
childData: '囍'
}
},
props: {
data: {
type: String,
default: ''
}
},
methods: {
childChange (data) {
this.childData += data
},
sendSang () {
this.$parent.changeSang(this.childData)
}
},
}
</script>
<style scoped>
</style>
方法五:$attrs / $listeners
多级组件嵌套需要传递数据时,通常使用的方法是通过vuex。但如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,未免有点大材小用。为此Vue2.4 版本提供了另一种方法----attrs/attrs/listeners
attrs:包含了父作用域中不被prop所识别(且获取)的特性绑定(class和style除外)。当一个组件没有声明任何prop时,这里会包含所有父作用域的绑定(class和style除外),并且可以通过v−bind="attrs:包含了父作用域中不被prop所识别(且获取)的特性绑定(class和style除外)。当一个组件没有声明任何prop时,这里会包含所有父作用域的绑定(class和style除外),并且可以通过v−bind=“attrs” 传入内部组件。通常配合 interitAttrs 选项一起使用。
listeners:包含了父作用域中的(不含.native修饰器的)v−on事件监听器。它可以通过v−on="listeners:包含了父作用域中的(不含.native修饰器的)v−on事件监听器。它可以通过v−on=“listeners” 传入内部组件
父组件代码:
<template>
<div class="parent_view">
<div class="item_view">
<h1>父组件</h1>
</div>
<div class="item_view">
<child5
:auther="auther"
:language="language"
:skill="skill"
:habit="habit"
title="前端攻城狮~w(゚Д゚)w"
></child5>
</div>
</div>
</template>
<script>
import child5 from './children/Children5.vue'
export default {
name: 'parentView',
data () {
return {
auther: 'lookingForw_4585',
language: 'javascript',
skill: 'css / html5',
habit: 'Music'
}
},
components: {
child5,
},
}
</script>
子组件代码:
<template>
<div class="child_1">
<h1>子组件5</h1>
<p>接收来自父组件的auther</p>
<p style="color:#1ee459;">{{auther}}</p>
<p>接收来自父组件的$attrs</p>
<p>{{$attrs}}</p>
<hr />
<childItem1 v-bind="$attrs"></childItem1>
</div>
</template>
<script>
import childItem1 from './items/ChildrenItems2'
export default {
name: 'child_5',
data () {
return {
}
},
components: {
childItem1
},
inheritAttrs: false, // 以关闭自动挂载到组件根元素上的没有在props声明的属性
props: {
auther: { // auther作为props属性绑定
type: String,
default: ''
}
},
methods: {
},
}
</script>
<style scoped>
.child_1 {
overflow-x: auto;
}
</style>
儿子组件:
<template>
<div class="child_1">
<h3>子组件5_1</h3>
<p>接收来自父组件的language</p>
<p style="background-color:#999999;">{{language}}</p>
<p>子组件5_1的$attrs:</p>
<p>{{$attrs}}</p>
<hr />
<childItem3 v-bind="$attrs"></childItem3>
</div>
</template>
<script>
import childItem3 from './ChildrenItems3'
export default {
name: 'child_5_1',
data () {
return {
}
},
components: {
childItem3
},
inheritAttrs: false,
props: {
language: {
type: String,
default: ''
}
},
methods: {
},
mounted () {
}
}
</script>
<style scoped>
</style>
孙子组件:
<template>
<div class="child_1">
<h3>子组件5_1_1</h3>
<p style="color:red;">{{title}}</p>
<p>接收来自父组件的$attrs</p>
<p style="color:cyan;">{{$attrs}}</p>
</div>
</template>
<script>
export default {
name: 'child_5_1_1',
data () {
return {
}
},
props: {
skill: {
type: String,
default: ''
},
title: {
type: String,
default: ''
}
},
methods: {
},
mounted () {
}
}
</script>
<style scoped>
</style>
效果如图:
如上图所示attrs表示没有继承数据的对象,格式为属性名:属性值。Vue2.4提供了attrs表示没有继承数据的对象,格式为属性名:属性值。
Vue2.4提供了attrs , listeners来传递数据与事件,跨级组件之间的通讯变得更简单。简单来说:listeners来传递数据与事件,跨级组件之间的通讯变得更简单。简单来说:attrs与listeners是两个对象,listeners是两个对象,attrs 里存放的是父组件中绑定的非 Props 属性,$listeners里存放的是父组件中绑定的非原生事件。
方法六 vuex
vuex是vue官方提供的状态管理工具,当项目较复杂,部分数据在多个组件中使用时,使用vuex是首选选择。具体的用法就不在赘述,网络上有很多教程和文章介绍。
最后附上上面gif图片的父组件代码。
<template>
<div class="parent_view">
<div class="item_view">
<h1>父组件</h1>
<div style="text-align:left;">当前时间为:</div>
<div class="time_view">{{currentTime}}</div>
<button @click="send">向子组件发送时间</button>
<p style="text-align:left;">message</p>
<p style="text-align:left;color:red;">{{massage}}</p>
<p style="text-align:left;">sangName</p>
<p style="text-align:left;color:plum;">{{sangName}}</p>
</div>
<div class="item_view">
<h1>子组件1</h1>
<child1 :cTime="currentTime" @getChildData="getChildData"></child1>
</div>
<div class="item_view">
<child2></child2>
</div>
<div class="item_view">
<child3 v-model="massage"></child3>
</div>
<div class="item_view">
<child4 :sangName="sangName"></child4>
</div>
<div class="item_view">
<child5
:auther="auther"
:language="language"
:skill="skill"
:habit="habit"
title="前端攻城狮~w(゚Д゚)w"
></child5>
</div>
</div>
</template>
<script>
import child1 from './children/Children1.vue'
import child2 from './children/Children2.vue'
import child3 from './children/Children3.vue'
import child4 from './children/Children4.vue'
import child5 from './children/Children5.vue'
import bus from '../utils/bus.js'
export default {
name: 'parentView',
data () {
return {
currentTime: '',
massage: '这是简单的组件传值的例子!',
sangName: '现在听的歌叫《Please Don’t Touch》',
auther: 'lookingForw_4585',
language: 'javascript',
skill: 'css / html5',
habit: 'Music'
}
},
components: {
child1,
child2,
child3,
child4,
child5,
},
methods: {
getChildData (data) {
this.currentTime = data;//将子组件传递的数据显示在父组件后面
},
send () {
bus.$emit("sendChild", this.currentTime);
},
changeSang (data) {
this.sangName += data
},
sendSang () {
this.$children[3].childChange(this.$children[3].childData)
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.parent_view {
width: 90%;
height: 750px;
margin: 0 auto;
border: 1px solid cadetblue;
border-radius: 5px;
display: flex;
justify-content: center;
align-items: center;
}
.item_view {
flex: 1;
margin: 10px 10px;
height: 95%;
border: 1px solid cyan;
border-radius: 5px;
overflow: auto;
}
.time_view {
color: cornflowerblue;
font-weight: 800;
text-align: center;
margin: 10px;
}
</style>
tips:
如果本文对您有帮助,欢迎评论告知。