前言
组件是 vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用。一般来说,组件可以有以下几种关系:
![](https://img.haomeiwen.com/i12889658/a571560ad5912492.png)
如上图所示,A 和 B、B 和 C、B 和 D 都是父子关系,C 和 D 是兄弟关系,A 和 C 是隔代关系(可能隔多代)。
针对不同的使用场景,如何选择行之有效的通信方式?这是我们所要探讨的主题。本文总结了vue组件间通信的几种方式,如props、
方法一:props/$emit
父组件A通过props的方式向子组件B传递,B to A 通过在 B 组件中 $emit, A 组件中 v-on 的方式实现。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue组件通信方式盘点——props/$emit</title>
</head>
<body>
<div id="app">
<child :title='title' v-on:parent-on-click='parentClick'></child>
<div v-if='childParams'>我是{{childParams}}传过来的参数!</div>
</div>
<script src="../js/vue.js"></script>
<script>
// 子组件
Vue.component('child', {
props: {
title: String
},
data() {
return {
name: '子组件'
}
},
template: `<div @click='onClick'>我是{{title}}传过来的参数!</div>`,
methods: {
onClick() {
this.$emit('parent-on-click', this.name)
}
},
})
// 父亲组件
new Vue({
el: '#app',
data() {
return {
title: '父组件',
childParams: ''
}
},
methods: {
parentClick($event) {
this.childParams = $event
}
},
})
</script>
</body>
</html>
方法二:eventbus
这种方法通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue组件通信方式盘点——$on/$emit</title>
</head>
<body>
<div id="app">
<div @click='onClickA'>A组件</div>
<h3 v-if='params'>{{params}}</h3>
<b-child></b-child>
</div>
<script src="../js/vue.js"></script>
<script>
class Bus {
constructor() {
this.callbacks = {}
}
$on(name, fn) {
this.callbacks[name] = this.callbacks[name] || []
this.callbacks[name].push(fn)
}
$emit(name, args) {
if (this.callbacks[name]) {
this.callbacks[name].forEach(cb => cb(args))
}
}
}
Vue.prototype.$bus = new Bus()
// 或者
// Vue.prototype.$bus=new Vue()
// 子组件C
Vue.component('c-child', {
template: `
<div>
<div @click='onClick'>C组件</div>
</div>
`,
methods: {
onClick() {
this.$bus.$emit('params', '我是C组件!')
console.dir(this.$bus)
}
},
})
// 子组件D
Vue.component('d-child', {
template: `
<div>
<div>D组件</div>
<h3 v-if='params'>{{params}}</h3>
</div>
`,
data() {
return {
params: ''
}
},
created() {
this.$bus.$on('params', args => {
this.params = args
})
},
})
// 子组件B
Vue.component('b-child', {
data() {
return {
params: ''
}
},
template: `
<div>
<p>B组件</p>
<h3 v-if='params'>{{params}}</h3>
<c-child></c-child>
<d-child></d-child>
</div>
`,
created() {
this.$bus.$on('params', args => {
console.log(args)
this.params = args
});
},
mounted() {
// this.$bus.$on('params', args => {
// console.log(args)
// this.params = args
// });
},
})
// 父亲组件(A组件)
new Vue({
el: '#app',
name: 'A',
data() {
return {
params: ''
}
},
created() {
this.$bus.$on('params', args => {
this.params = args
});
},
methods: {
onClickA() {
this.$bus.$emit('params', '我是A组件')
console.dir(this.$bus)
}
},
})
</script>
</body>
</html>
Vue.prototype.
bus = new Vue()都可以!
方法三:vuex
创建唯一的全局数据管理者store,通过它管理数据并通知组件状态变更。(此处略过,请查看官网)https://vuex.vuejs.org/zh/
方法四:$parent/$root
兄弟组件之间通信可通过共同祖辈搭桥,$parent或$root
。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue组件通信方式盘点——$parent/$children/ref</title>
</head>
<body>
<div id="app">
<div>A组件</div>
<b-child></b-child>
</div>
<script src="../js/vue.js"></script>
<script>
// 子组件C
Vue.component('c-child', {
template: `
<div>
<div>C组件</div>
<button @click='onClick'>通过$parent更改组件状态</button>
<button @click='withRoot'>通过$root更改组件状态</button>
<h3 v-if='params'>{{params}}</h3>
</div>
`,
data() {
return {
params: ''
}
},
methods: {
onClick() {
this.$parent.$emit('params','C组件通过'+this.$parent.name+'组件')
},
withRoot() {
this.$root.$emit('params','C组件通过'+this.$root.name+'组件')
}
},
})
// 子组件D
Vue.component('d-child', {
template: `
<div>
<div>D组件</div>
<h3 v-if='params'>{{params}}</h3>
</div>
`,
data() {
return {
params: ''
}
},
created () {
this.$parent.$on('params',args=>{
this.params=args+'操作了D组件!'
});
this.$root.$on('params',args=>{
this.params=args+'操作了D组件!'
});
},
})
// 子组件B
Vue.component('b-child', {
data() {
return {
name: 'B'
}
},
template: `
<div>
<p>B组件</p>
<c-child></c-child>
<d-child></d-child>
</div>
`,
})
// 父亲组件(A组件)
new Vue({
el: '#app',
data() {
return {
name: 'root'
}
},
})
</script>
</body>
</html>
方法五:$children/ref
父组件可以通过$children/ref访问子组件实现父子通信。
// parent $children
this.$children[0].xx = 'xxx'
// parent $refs
<HelloWorld ref="hw"/>
mounted() {
this.$refs.hw.xx = 'xxx'
}
注意:$children不能保证子元素顺序
方法六:$attrs/$listeners
包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 ( class 和 style 除外)。当一个组件没有
声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外),并且可以通过 v- bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue组件通信方式盘点——$attrs/$listeners</title>
</head>
<body>
<div id="app">
<div>A组件</div>
<b-child :data='data' @click='cb'></b-child>
<h3>{{cbData}}</h3>
</div>
<script src="../js/vue.js"></script>
<script>
// 子组件B
Vue.component('b-child', {
template: `
<div>
<p>B组件</p>
<p>$attrs:{{$attrs}}</p>
<button @click='$listeners.click("子页面被点击啦!")'>回调父级事件</button>
</div>
`,
created() {
console.dir(this.$listeners);
},
})
// 父亲组件(A组件)
new Vue({
el: '#app',
data() {
return {
data: 'root',
cbData: ''
}
},
methods: {
cb($event) {
console.log($event)
this.cbData = $event
}
},
})
</script>
</body>
</html>
方法七:provide/inject
能够实现祖先和后代之间传值
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue组件通信方式盘点——provide/inject</title>
</head>
<body>
<div id="app">
<div>A组件</div>
<b-child></b-child>
</div>
<script src="../js/vue.js"></script>
<script>
// 子组件B
Vue.component('b-child', {
template: `
<div>
<p>B组件</p>
<p>{{parentName}}</p>
</div>
`,
inject:['parentName']
})
// 父亲组件(A组件)
new Vue({
el: '#app',
provide(){
return {
parentName:'A页面'
}
}
})
</script>
</body>
</html>
总结
常见使用场景可以分为三类:
父子通信: 父向子传递数据是通过 props,子向父是通过 events(
$emit
);通过父链 / 子链也可以通信($parent/$children
);ref 也可以访问组件实例;provide / inject API;$attrs/$listeners
兄弟之间通信: $bus、Vuex
跨级通信:
$attrs/$listeners
,$bus, Vuex, provide/inject,
网友评论