浅谈vue组件间通讯
如何进行父子组件的通信?
父组件通过import引入子组件,并在父组件中通过component属性注册,然后子组件就可以用标签的形式嵌进父组件了。
props
子组件通过props选项能够接收到来自父组件的数据。props是单向绑定的,即只能父组件向子组件传递数据,不能反向。并且传递方式也有两种:
-
静态传递:子组件通过props来声明一个自定义的属性,但这个属性是静态的,不能发生改变。通常用来传字符串。
-
动态传递:通过v-bind绑定props的自定义属性,传递过去可以是一个表达式、布尔值、对象等。
//父组件
<template>
<div class="item">
<TodoItem :msg="a+b"></TodoItem>
<TodoItem v-bind:msg="msg"></TodoItem>
</div>
</template>
<script>
import TodoItem from "./components/TodoItem";
export default{
components:{
TodoItem
},
data(){
return {
a:'我是组件1',
b:'我是组件2',
msg:'我是组件'+Math.random()
}
}
}
</script>
<style scoped>
.item{
margin: 20px auto 20px 20px;
}
</style>
//子组件
<template>
<div>
<p>{{msg}}</p>
</div>
</template>
<script>
export default {
name: "TodoItem",
props:['msg']
}
</script>
$ref
对于ref官方的解释是:ref 是被用来给元素或子组件注册引用信息的。引用信息将会注册在父组件的 $refs 对象上。
说这些可能比较抽象,简单来说就是:
-
ref作用到子组件上时,指向的是子组件的实例,通过$refs可能获取到在子组件里定义的属性和方法
-
ref作用到DOM元素上时,指向的就是DOM元素,通过$refs可能获取到该DOM的属性集合,轻松访问到DOM元素,作用与JQ选择器类似。
//父组件
<template>
<div class="item">
<TodoItem ref="msg"></TodoItem>
<p ref="dom"></p>
</div>
</template>
<script>
import TodoItem from "./components/TodoItem.vue";
export default{
components:{
TodoItem
},
mounted() {
console.log(this.$refs)
this.$refs.msg.getmsg("我是fw");
this.$refs.dom.innerHTML="我是普通元素哦";
}
}
</script>
<style scoped>
.item{
margin: 20px auto 20px 20px;
}
</style>
//子组件
<template>
<div>
<p>{{msg}}</p>
</div>
</template>
<script>
export default {
name: "TodoItem",
data(){
return{
msg:''
}
},
methods:{
getmsg(s){
this.msg=s;
}
}
}
</script>
props与$ref的区别:
props着重于数据的传递,它并不能调用子组件的属性和方法。自定义标题和内容适合用props.
ref着重于索引,主要用来调用子组件中的属性和方法,其实并不擅长数据传递。而且ref用在DOM元素的时候,有选择器的作用,这个功能比索引更常用到。
$emit
props和ref主要都是父组件向子组件通信,而$emit主要用于子组件向父组件通信。
vm.$emit(event,arg)
$emit 绑定一个自定义事件event,当这个这个语句被执行到的时候,就会将参数arg传递给父组件,父组件通过@event监听并接收参数。
//父组件
<template>
<div class="item">
<TodoItem @getMsg="showMsg"></TodoItem>
{{title}}
</div>
</template>
<script>
import TodoItem from "./components/HelloWorld.vue";
export default{
components:{
TodoItem
},
data(){
return{
title:''
}
},
methods:{
showMsg(title){
this.title=title;
}
}
}
</script>
<style scoped>
.item{
margin: 20px auto 20px 20px;
}
</style>
//子组件
<template>
<div>
<p>子组件</p>
</div>
</template>
<script>
export default {
mounted() {
this.$emit('getMsg','我从子组件过来的')
}
}
</script>
$parent - $children
可以直接获取组件的实例, 但是缺点就是只能父子之间, 并且直接操作组件实例不是一个好的选择。
$parent 就是父组件的实例对象,而 $children 就是当前实例的直接子组件实例了,不过这个属性值是数组类型(子组件实例组成的数组)的,且并不保证顺序,也不是响应式的。
// 定义 parent 组件
Vue.component('parent', {
template: `
<div>
<p>this is parent component!</p>
<button @click="changeChildValue">test</button>
<child />
</div>
`,
data() {
return {
message: 'hello'
}
},
methods: {
changeChildValue(){
this.$children[0].mymessage = 'hello';
}
},
});
// 定义 child 组件
Vue.component('child', {
template:`
<div>
<input type="text" v-model="mymessage" @change="changeValue" />
</div>
`,
data() {
return {
mymessage: this.$parent.message
}
},
methods: {
changeValue(){
this.$parent.message = this.mymessage;//通过如此调用可以改变父组件的值
}
},
});
const app = new Vue({
el: '#app',
template: `
<div>
<parent />
</div>
`
});
边界:
- 最深层的子组件的 this.$children返回的是一个空数组
- App.vue的this. $parent返回的是new Vue()
这种方式不推荐使用, 会造成项目结构混乱
祖孙组件间通信
事件总线
new一个空的vue实例, 利用它本身的 o n / on/ on/emit实现一个中心的状态管理, 接收和发布事件。
// 组件 A
Vue.component('A', {
template: `
<div>
<p>this is A component!</p>
<input type="text" v-model="mymessage" @input="passData(mymessage)">
</div>
`,
data() {
return {
mymessage: 'hello brother1'
}
},
methods: {
passData(val) {
//触发全局事件globalEvent
this.$EventBus.$emit('globalEvent', val)
}
}
});
// 组件 B
Vue.component('B', {
template:`
<div>
<p>this is B component!</p>
<p>组件A 传递过来的数据:{{brothermessage}}</p>
</div>
`,
data() {
return {
mymessage: 'hello brother2',
brothermessage: ''
}
},
mounted() {
//绑定全局事件globalEvent
this.$EventBus.$on('globalEvent', (val) => {
this.brothermessage = val;
});
}
});
//定义中央事件总线
const EventBus = new Vue();
// 将中央事件总线赋值到 Vue.prototype 上,这样所有组件都能访问到了
Vue.prototype.$EventBus = EventBus;
const app = new Vue({
el: '#app',
template: `
<div>
<A />
<B />
</div>
`
});
provide-inject
常用于组件和后代元素之间的通信,因为使用props的并且有好几级的跨越,那么每次都要声明接收并且传给子组件,很繁琐。
使用provide-inject的话,==不需要声明接收,也不需要向下传递,只要祖先元素provide数据即可,==可以是数值、函数,也可以提供多个,但是数据进行provider时,vue实例还未创建,所以this是undefined
其传递的对象和函数的话是响应式的,因为传递是其引用,如果是基本类型就不是响应式的
// 组件
export default {
provide: {
example: 'provide',
example2: () => {
console.log(this);
},
},
}
// 后代组件
export default {
inject: ['example', 'example2'],
mounted() {
console.log(this.example) // provide
this.example2() // undefined
},
}
$attrs与 $listeners
- $ attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定属性 (class和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件。
- $ listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件。
// 组件A
Vue.component('A', {
template: `
<div>
<p>this is parent component!</p>
<B :messagec="messagec" :message="message" v-on:getCData="getCData" v-on:getChildData="getChildData(message)"></B>
</div>
`,
data() {
return {
message: 'hello',
messagec: 'hello c' //传递给c组件的数据
}
},
methods: {
// 执行B子组件触发的事件
getChildData(val) {
console.log(`这是来自B组件的数据:${val}`);
},
// 执行C子组件触发的事件
getCData(val) {
console.log(`这是来自C组件的数据:${val}`);
}
}
});
// 组件B
Vue.component('B', {
template: `
<div>
<input type="text" v-model="mymessage" @input="passData(mymessage)">
<!-- C组件中能直接触发 getCData 的原因在于:B组件调用 C组件时,使用 v-on 绑定了 $listeners 属性 -->
<!-- 通过v-bind 绑定 $attrs 属性,C组件可以直接获取到 A组件中传递下来的 props(除了 B组件中 props声明的) -->
<C v-bind="$attrs" v-on="$listeners"></C>
</div>
`,
/**
* 得到父组件传递过来的数据
* 这里的定义最好是写成数据校验的形式,免得得到的数据是我们意料之外的
*
* props: {
* message: {
* type: String,
* default: ''
* }
* }
*
*/
props: ['message'],
data(){
return {
mymessage: this.message
}
},
methods: {
passData(val){
//触发父组件中的事件
this.$emit('getChildData', val)
}
}
});
// 组件C
Vue.component('C', {
template: `
<div>
<input type="text" v-model="$attrs.messagec" @input="passCData($attrs.messagec)">
</div>
`,
methods: {
passCData(val) {
// 触发父组件A中的事件
this.$emit('getCData',val)
}
}
});
var app=new Vue({
el:'#app',
template: `
<div>
<A />
</div>
`
});
总结
父子通信
props,events,ref,parent/children,provide/inject,事件总线
兄弟或其他不相关的通信
事件总线
祖孙
props,events,attrs/listeners,provide/inject,事件总线
如果大家有什么意见或建议希望大家在评论区多多交流,谢谢😊