vue是数据驱动视图更新的框架, 所以对于vue来说组件间的数据通信非常重要,那么组件之间如何进行数据通信的呢?首先我们需要知道在vue中组件之间存在什么样的关系, 一般来说,组件可以有以下几种关系:
如上图所示, A与B、A与C、B与D、C与E组件之间是父子关系;B与C之间是兄弟关系;A与D、A与E之间是隔代关系;D与E是堂兄关系(非直系亲属) 针对以上关系我们归类为:
组件是 vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用。针对不同的使用场景,如何选择行之有效的通信方式?这是我们所要探讨的主题。本文总结了vue组件间通信的几种方式,希望对小伙伴有些许帮助。
父组件A通过props
的方式向子组件B传递,B to A 通过在 B 组件中 $emit,
A 组件中 v-on 的方式实现。
下面通过一个例子说明父组件如何向子组件传递数据:在子组件中如何获取父组件中的字符类型数据title:“父组件向子组件传值”,数组对象类型数据list[:{ city:‘武汉’, postil :“英雄” },{ city: ‘北京’, postil:“首都” }],对象类型数据:catalogue:{keyWord:“中国四大名著”},数组型数据:articleList: [‘红楼梦’, ‘西游记’, ‘三国演义’,“水浒传”];
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vue-2.6.11</title>
<script src="../../vue.js"></script>
</head>
<body>
<div id="app">
<!-- 需要用“v-bind”来告诉 Vue传给prop一个什么类型的值;
左边":"后为父组件传入的变量名;右边为子组件props接受的变量名-->
<root-temp :mytitle="title" :userlist="list" :catalogue.keyWord="catalogue" :articles="articleList"></root-temp>
</div>
<script type="text/template" id="childTemp">
<div>
<!-- 获取一个变量 -->
<h2>{
{
mytitle}}</h2>
<ul>
<!-- 获取一个数组下的对象 -->
<li v-for="user in userlist">
{
{
user.city}}-{
{
user.postil }}
</li>
</ul>
<!-- 获取一个对象 -->
<h2>{
{
catalogue.keyWord}}</h2>
<ol>
<!-- 获取一个数组 -->
<li v-for="(item, index) in articles" :key="index">{
{
item}}</li>
</ol>
</div>
</script>
<script>
//全局注册的父组件
Vue.component('root-temp', {
props: ['mytitle', "userlist","catalogue","articles"],
//子组件
template: "#childTemp",
})
var vm = new Vue({
el: "#app",
data: {
title: "父组件向子组件传值",
list: [
{
city: '武汉', postil : "英雄" },
{
city: '北京', postil : "首都" }
],
catalogue:{
keyWord:"中国四大名著"
},
articleList: ['红楼梦', '西游记', '三国演义',"水浒传"]
},
})
</script>
</body>
</html>
用“v-bind”
来告诉 Vue传给prop
一个什么类型的值; 子组件用props获取数据; prop
只可以从上一级组件传递到下一级组件(父子组件),即所谓的单向数据流。而且 prop
只读,不可被修改,所有修改都会失效并警告。
缺点:如果组件嵌套层次多的话,数据传递比较繁琐。
对于$emit
我觉得理解是这样的: $emit
绑定一个自定义事件, 当这个语句被执行时, 就会将参数arg传递给父组件,父组件通过v-on监听并接收参数。通过一个例子,说明子组件如何向父组件传递数据。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../vue.js"></script>
</head>
<body>
<div id="app">
{
{
count}}
<gp-counter @counterchange="handleChange"></gp-counter>
</div>
<script type="text/template" id="counterTemp">
<div>
<h1>gp-counter</h1>
<div>
<button @click="decrement(1)">-</button>
<button @click="increment(1)">+</button>
</div>
</div>
</script>
<script>
Vue.component('gp-counter', {
template: "#counterTemp",
//组件的data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝,不然一个实例的data改变所有实例的date的引用都会改变,:
data() {
return {
//子组件将要向外发送的数据:给个初始值为0
count: 0
}
},
methods: {
//子组件中对加减事件进行侦听,操作数据驱动显示
increment(num) {
this.count++;
//数据改变时,使用$emit向外发布“counterchange”事件,并将数据放在第二个参数传递;
this.$emit('counterchange', this.count)
},
decrement(num) {
this.count--;
this.$emit('counterchange', this.count)
}
}
})
var vm = new Vue({
el: "#app",
data: {
count: 0
},
methods: {
handleChange(num) {
this.count = num;
}
}
})
</script>
</body>
</html>
总结:在子组件中对加减事件进行侦听,操作数据驱动显示;数据改变时,使用$emit
向外发布“counterchange
”事件,并将数据放在第二个参数传递;在父组件里通过@counterchange="handleChange"
就可以订阅handleChange
事件了;handleChange
函数拿到的参数赋给实例data选项中的一个属性;就可以在父级组件之间使用。
缺点:如果组件嵌套层次多的话,数据传递比较繁琐。
概念:provide/ inject 是vue2.0新增的api,简单来说就是父组件中通过provide来提供变量, 然后再子组件中通过inject来注入变量;主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。接下来就用一个例子来验证上面的描述: 假设有三个组件: A、B、C 其中 C是B的子组件,B是A的子组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../vue.js"></script>
</head>
<body>
<div id="app">
<compa></compa>
</div>
<script>
//A组件
const compa = {
template: `
`,
//B组件
components: {
inject: ['user'],
compb:{
template: `
“儿子”组件------{
{user}}
`,
components: {
compc
}
}
}
}
//C组件
const compc = {
//利用inject实现注入变量username
inject: ['username'],
template: '“孙子”组件------{
{username}}
'
}
var vm = new Vue({
el: "#app",
data: {
username: 'apple'
},
//provide函数结合data选项配置数据
provide: function () {
return {
username: this.username
}
},
components: {
compa
}
})
</script>
</body>
</html>
通过对比来看这里不论子组件嵌套有多深, 只要调用了inject 那么就可以注入provide中的数据,而不局限于只能从当前父组件的props属性中回去数据;注意配置和注入的变量名要保持一致。
缺点:provide inject
(依赖注入)不支持响应式
$root
、$parent
、$refs
1、$root
Vue 子组件可以通过$root
属性获取vue的根实例,比如在简单的项目中将公共数据放再vue根实例上(可以理解为一个全局 store
),因此可以代替vuex实现状态管理;
2、$parent
属性可以用来从一个子组件访问父组件的实例,可以替代将数据以 prop
的方式传入子组件的方式;当变更父级组件的数据的时候,容易造成调试和理解难度增加;
3、在子组件上使用ref
特性后,this.$refs
属性可以直接访问该子组件。可以代替事件$emit
和$on
的作用。使用方式是通过 ref
特性为这个子组件赋予一个 ID 引用,再通过this.$refs.testId
获取指定元素。注意:$refs
只会在组件渲染完成之后生效,并且它们不是响应式的。这仅作为一个用于直接操作子组件的“逃生舱”——你应该避免在模板或计算属性中访问 $refs
。
通过一个Demo具体看一下
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../vue.js"></script>
</head>
<body>
<div id="app">
<!--父组件 -->
<root-component></root-component>
</div>
<script>
//子组件
Vue.component('root-component', {
template: `
`,
methods: {
getHandler() {
console.log("root-component-->this:",this)
console.log("root-component-->this.$root:",this.$root)
console.log("root-component-->this.$parent:",this.$parent)
}
}
})
//孙子组件
Vue.component('child-component', {
template: `
`,
methods: {
getHandler() {
console.log("child-component-->this:",this)
console.log("child-component-->this.$root:",this.$root)
console.log("child-component-->this.$parent:",this.$parent)
}
}
})
var app = new Vue({
el: '#app',
data: {
message: 'Root'
}
})
</script>
</body>
</html>
通过观察Dom结构组件有三级,分别在子组件和孙子组件中侦听click事件;在对应getHandler事件中获取对应的this
、this.$root
、this.$parent
;通过对比在两级组件中的结果可以看出:
root 和parent 的区别
root 和parent 都能够实现访问父组件的属性和方法,两者的区别在于,如果存在多级子组件,通过parent 访问得到的是它最近一级的父组件,通过root 访问得到的是根父组件。
所有子组件都可以将这个实例作为一个全局 store 来访问或使用
// 获取根组件的数据
this.$root.message
// 写入根组件的数据
this.$root.message= 2
// 访问根组件的计算属性
this.$root.selfdate
// 调用根组件的方法
this.$root.selfmethods()
$refs
访问子组件实例;通过在子组件标签定义 ref 属性,在父组件中可以使用$refs
访问子组件实例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../vue.js"></script>
</head>
<body>
<div id="app">
<button @click="add">通过ref访问子组件</button>
<input type="text" ref="inputDate"/>
</div>
<script>
var app = new Vue({
el: '#app',
methods: {
add:function(){
console.log("获取子组件的input.value---->",this.$refs.inputDate.value)
this.$refs.inputDate.value ="test"; //this.$refs.inputDate 减少获取dom节点的消耗
console.log("获取更改后的子组件input.value---->",this.$refs.inputDate.value)
}
}
})
</script>
</body>
</html>
eventBus 又称为事件总线,在vue中可以使用它来作为沟通桥梁的概念, 就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件, 所以组件都可以通知其他组件。在Vue的项目中怎么使用eventBus来实现组件之间的数据通信呢?具体通过下面几个步骤
首先需要创建一个事件总线并将其导出, 以便其他模块可以使用或者监听它.
var eventbus = new Vue();
methods: {
handleClick() {
eventbus.$emit('message', 'hello world')
}
}
const compb = {
template: 'EventBus-componentb
',
mounted() {
eventbus.$on('message', function (msg) {
console.log(msg)
})
}
}
如果需要移除事件的监听:
mounted() {
eventbus.$off('message', function (msg) {
console.log(msg)
})
}
我们通过完整的Domo看一下效果:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../vue.js"></script>
</head>
<body>
<div id="app">
<compa :user="username"></compa>
<button @click="handleClick">send</button>
</div>
<script>
var eventbus = new Vue();
const compb = {
template: 'EventBus-componentb
',
mounted() {
eventbus.$on('message', function (msg) {
console.log('组件中获取的message===>',msg)
})
eventbus.$off('message', function (msg) {
console.log("侦听器已经移除")
})
}
}
const compa = {
template: `
EventBus-componenta
`,
components: {
compb
}
}
var vm = new Vue({
el: "#app",
data: {
username: 'china'
},
components: {
compa
},
methods: {
handleClick() {
eventbus.$emit('message', 'hello world')
}
}
})
</script>
</body>
</html>
缺点:eventbus
方式数据不支持响应式;当项目较大,维护起来也比较困难。
Vuex实现了一个单向数据流,在全局拥有一个State存放数据,当组件要更改State中的数据时,必须通过Mutation进行,Mutation同时提供了订阅者模式供外部插件调用获取State数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走action,但action也是无法直接修改State的,还是需要通过Mutation来修改State的数据。最后,根据State的变化,渲染到视图上。
Vue Components
:Vue组件。HTML页面上,负责接收用户操作等交互行为,执行dispatch方法触发对应action进行回应。
dispatch:
操作行为触发方法,是唯一能执行action的方法。
actions:
操作行为处理模块,由组件中的 $store.dispatch(‘action 名称’,data)来触发。然后由commit()来触发mutation的调用 , 间接更新 state。负责处理Vue Components接收到的所有交互行为。
commit:
状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法。
mutations:
状态改变操作方法,由actions中的 commit(‘mutation 名称’)来触发。是Vuex修改state的唯一推荐方法。该方法只能进行同步操作,且方法名只能全局唯一。
state:
页面状态管理容器对象。集中存储Vue components中data对象的零散数据,全局唯一,以进行统一的状态管理。
getters:
Vue Components通过该方法读取全局state对象。
如何要详细了解Vuex实现组件间的数据通信欢迎访问下一篇博客
我们可以按组件之间的三种关系简单归纳一下组件之间的通信方式:
父子通信:
父组件向子组件传递数据可以通过 props
;
子组件向父组件是通过 $emit
、$on
事件;
provide / inject
;
还可以通过 $root
、$parent
、$refs
属性相互访问组件实例;
兄弟通信: eventbus
;Vuex
;
跨级通信: eventbus
;Vuex
;provide / inject;