Vue组件通信方式

一、组件间存在的关系

通常来说,各组件之间的关系分为:

• 父子组件
• 兄弟组件
• 隔代组件

问题来了,在Vue中,如何界定各组件之间属于什么关系?

在Vue中,组件以树的形式互相联结:

个人的理解是,组件之间的引入关系决定了各自之间的从属关系:

对于父子组件,如果组件B在组件A中通过以下方式注册,且在组件A的template以如下方式引用:

//组件A-父组件
<template>
  <div>
	  <ComponentB></ComponentB>
	  <ComponentC></ComponentC>
  </div>
</template>

<script>
import ComponentB from './ComponentB.vue'
import ComponentC from './ComponentC.vue'
export default {
 components: {
   ComponentB,
   ComponentC
 },
 // ...
}
  </script>
//组件B-子组件
<template>
  <div>
	<p>How are you</p>
  </div>
</template>

<script>
export default {
  // ...
}
</script>
//组件C
<template>
  <div>
	<p>I am fine</p>
  </div>
</template>

<script>
export default {
  // ...
}
</script>

则组件A为父组件,组件B为子组件;同理,A组件与C组件也为父子组件

B组件和C组件都在A组件的template中被引用,所以B、C为兄弟组件。其余情况的复杂关系,称为隔代组件.

实际开发中,可以将组件之间的关系分为两大类:

第一类: 有引入关系,父子组件
第二类: 没有引入关系,非父子组件

二、父子组件通信

2.1 子组件获取/访问父组件中的数据
法1:Prop

在父组件中可传递动态或静态的prop给子组件,子组件中通过props接收数据:

例如,父组件A需要传递message字段给子组件B:

//组件A-父组件
<template>
  <div>
    //父组件中采用v-bind形式传递动态Prop
     <ComponentB :message="message"></ComponentB>
  </div>
</template>
<script>
import ComponentB from './ComponentB.vue'
export default {
  components: {
    ComponentB
  },
  data () {
    return {
        message: '123'
    }
  }
}
</script>

子组件B接收数据:

//组件B-子组件
<template>
  <div>
      <p>{{ message }</p>
  </div>
</template>
<script>
export default {
  props: ['message'],
  data () {
    return {
        ...
    }
  }
}
</script>

由于Prop的单向数据流特性,子组件中不能直接修改props的值。

如果遇到必须在子组件中改变props的情况,官方文档给出了2种示例方案。

法2: $parent

$parent是Vue提供的API,用它可以在子组件中访问父组件的实例:

console.log(this.$parent)
以上方法的使用场景:

a. Props: 大多数情况下,可以优先考虑使用
b. $parent: 子组件需要调用父组件的方法或属性,并且并不涉及到修改父组件的数据时可以考虑使用(Vue是单向数据流动,所以不建议通过使用$parent修改父级组件的数据)

官方也强调了$parent的使用局限性:

此外,层级太深的时候,使用$parent并不方便。

2.2 父组件获取/访问子组件中的数据
法1:$emit触发事件

子组件通过调用 $emit方法 并传入事件名称及特定的参数(如果需要的话)来触发一个事件:

例如,子组件需要给父组件传回一个布尔值,以便告知父组件中的内容是否显示:

//组件B-子组件
<template>
  <div>
      <p @click="open">回到主页面</p>
  </div>
</template>
<script>
export default {
  data () {
    return {
        ...
    }
  }
  methods: {
    open() {
     //changeSomething为需要传递给父组件的事件名称,false为需要传递的参数
       this.$emit('changeSomething', false)
    }
  }  
}
</script>

父级组件通过v-on监听子组件的事件,并获取子组件传递的参数:

//组件A-父组件
<template>
  <div>
    //父组件v-on监听的事件名称与子组件中定义的事件名称一致
      <ComponentB
         v-if="showFlag" 
         @changeSomething="changeStatus">
      </ComponentB>
  </div>
</template>
<script>
import ComponentB from './ComponentB.vue'
export default {
  components: {
    ComponentB
  },
  data () {
    return {
        showFlag: true
    }
  }
   methods: {
    changeStatus(data) {
         this.showFlag = data;
    }
  }  
}
</script>
法2: $ref

可以通过$refAPI 为子组件赋予一个 ID 引用,如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例。

法3: $children

$children可以获取当前实例的直接子组件上的属性,用法类似与$parent

以上方法的使用场景:

a.$emit: 大多数情况下,可以优先考虑使用
b.$refs$children: 本身都不是响应式的,若只需要单纯获取数据而不需要数据绑定,可考虑用这两种方式

三、非父子组件通信

方法1: eventBus

示例场景: 组件B做了相关操作后,组件C中的某个数据需要发生相应变化(B和C不存在引入的关系)

第一步,在项目中初始化eventBus,以便能在组件中引入:

// main.js
//初始化全局的eventBus
import Vue from 'vue'
Vue.prototype.$EventBus = new Vue()

第二步:在组件B中通过this. e v e n t B u s . eventBus. eventBus.emit触发事件(可传递参数)

//组件B
<template>
  <div>
     <p @click="clickMe">我是B组件</p>
  </div>
</template>
<script>
export default {
  data () {
    return {
        ...
    }
  }
  methods: {
      clickMe() {
         this.$eventBus.$emit('changeC', msg)
    }
  }  
}
</script>

第三步:C组件中通过 this. e v e n t B u s . eventBus. eventBus.on监听来自B组件的事件

//组件c-
<template>
  <div>
      <p>我是C组件</p>
  </div>
</template>
<script>
export default {
  data () {
    return {
        ...
    }
  }
  mounted() {
      this.$eventBus.$on('changeC', (data) => {
        console.log(data)//B组件传过来的msg
    })
  }
  //移除移除自定义事件监听器
  destroyed() {
      this.$eventBus.$off('changeC');
  }
}
</script>

注意,为了避免事件被重复触发,需要组件销毁时,通过 $offAPI移除事件监听。

方法二:provide/inject

此方法类似于Props,适用于存在引入关系,但嵌套层级较深的隔代组件,例如爷孙组件: A、B为父子组件,B、C为父子组件,当C组件需要获取A组件的数据时,provide/inject就体现出了优势:

第一步,A组件中通过provide指定需要传递给子孙组件C的数据:

//组件A-父组件
<template>
  <div>
      <ComponentB></ComponentB>
  </div>
</template>
<script>
import ComponentB from './ComponentB.vue'
export default {
  components: {
    ComponentB
  },
  provide () {
    return {
       message: 'i am here with you'
    }
  }
  data () {
    return {
       
    }
  }
   methods: {
  }  
}
</script>

第二步:子孙组件C通过inject接收数据

//组件c
<template>
  <div>
      <p>我是C组件</p>
  </div>
</template>
<script>
export default {
  inject: [ "message" ],
  data () {
    return {
        ...
    }
  }
  mounted() {
    console.log(this.message);
  }
}
</script>

此种方法的缺点,官方解释如下:

方法三: $attrs/$listeners

$attrs包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。

$listeners包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件。

此处实践的并不多,所以不在此处描(luan)述(shuo)了,日后有深入的了解后再进行总结。

方法四: Vuex

关于Vuex的语法在此不做展开赘述,当两个没有直接引入关系的两个组件需要通信时,利用Vuex进行通信如下:

//组件B
<template>
 <div>
 <button @click="changeC">ClickMe</button>
 <p>{{ MessageFromC }}</p>
 </div>
</template>
 
<script>
 export default {
 data() {
 return {
 	MessageFromB: 'B组件中的数据'
 }
 },
 computed: {
	MessageFromC() {
	// 从store里获取的C组件的数据
	  return this.$store.state.MsgFromC
	}
 },
 methods: {
	changeC() {
	// 通过分发B组件中的改动,将B组件的数据存放至store
	this.$store.commit('receiveBMsg', {
	   MsgFromB: this.MessageFromB
	})
 }
 }
 }
</script>
//组件C
<template>
 <div>
 <button @click="changeB">传递数据给B组件</button>
 <p>{{ MessageFromB }}</p>
 </div>
</template>
 
<script>
 export default {
   data() {
     return {
     	MessageFromC: 'C组件中的数据'
     }
 },
 computed: {
   MessageFromB() {
   // 这里存储从store里获取的B组件的数据
   		return this.$store.state.MessageFromB
   }
 },
 methods: {
   changeB() {
   // 将C组件的数据存放至store,B组件中的数据随之发生变化
       this.$store.commit('receiveMsgFromC', {
       MsgFromC: this.MessageFromC
   })
 }
 }
 }
</script>
以上方法使用场景:

a.eventBus: 更适用于普通跨级别的组件间通信,但千万注意要移除事件监听。

b.provideinject : 主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。(provide 和 inject自身默认不是响应式的)

关于高阶组件,可以参考这位博主的博文。

c.$attrs/$listeners: 适用于高级别的组件(比如封装组件时)

d: Vuex: 适用于模块间耦合性高的项目,一方面方便对数据进行管理,另一方面也能进行跨组件的通信(数据的更改是响应式的)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值