vue中组件通信的八种方式(值得收藏!)


Posted in Javascript onAugust 09, 2019

前言

之前写了一篇关于vue面试总结的文章, 有不少网友提出组件之间通信方式还有很多, 这篇文章便是专门总结组件之间通信的

vue是数据驱动视图更新的框架, 所以对于vue来说组件间的数据通信非常重要,那么组件之间如何进行数据通信的呢?
首先我们需要知道在vue中组件之间存在什么样的关系, 才更容易理解他们的通信方式, 就好像过年回家,坐着一屋子的陌生人,相互之间怎么称呼,这时就需要先知道自己和他们之间是什么样的关系。

vue组件中关系说明:

vue中组件通信的八种方式(值得收藏!)

如上图所示, A与B、A与C、B与D、C与E组件之间是父子关系; B与C之间是兄弟关系;A与D、A与E之间是隔代关系; D与E是堂兄关系(非直系亲属)

针对以上关系我们归类为:

  • 父子组件之间通信
  • 非父子组件之间通信(兄弟组件、隔代关系组件等)

本文会介绍组件间通信的8种方式如下图目录所示:并介绍在不同的场景下如何选择有效方式实现的组件间通信方式,希望可以帮助小伙伴们更好理解组件间的通信。

vue中组件通信的八种方式(值得收藏!)

一、props / $emit

父组件通过props的方式向子组件传递数据,而通过$emit 子组件可以向父组件通信。

1. 父组件向子组件传值

下面通过一个例子说明父组件如何向子组件传递数据:在子组件article.vue中如何获取父组件section.vue中的数据articles:['红楼梦', '西游记','三国演义']

// section父组件
<template>
 <div class="section">
 <com-article :articles="articleList"></com-article>
 </div>
</template>

<script>
import comArticle from './test/article.vue'
export default {
 name: 'HelloWorld',
 components: { comArticle },
 data() {
 return {
 articleList: ['红楼梦', '西游记', '三国演义']
 }
 }
}
</script>
// 子组件 article.vue
<template>
 <div>
 <span v-for="(item, index) in articles" :key="index">{{item}}</span>
 </div>
</template>

<script>
export default {
 props: ['articles']
}
</script>

总结: prop 只可以从上一级组件传递到下一级组件(父子组件),即所谓的单向数据流。而且 prop 只读,不可被修改,所有修改都会失效并警告。

2. 子组件向父组件传值

对于$emit 我自己的理解是这样的: $emit绑定一个自定义事件, 当这个语句被执行时, 就会将参数arg传递给父组件,父组件通过v-on监听并接收参数。 通过一个例子,说明子组件如何向父组件传递数据。

在上个例子的基础上, 点击页面渲染出来的ariticle的item, 父组件中显示在数组中的下标

// 父组件中
<template>
 <div class="section">
 <com-article :articles="articleList" @onEmitIndex="onEmitIndex"></com-article>
 <p>{{currentIndex}}</p>
 </div>
</template>

<script>
import comArticle from './test/article.vue'
export default {
 name: 'HelloWorld',
 components: { comArticle },
 data() {
 return {
 currentIndex: -1,
 articleList: ['红楼梦', '西游记', '三国演义']
 }
 },
 methods: {
 onEmitIndex(idx) {
 this.currentIndex = idx
 }
 }
}
</script>
<template>
 <div>
 <div v-for="(item, index) in articles" :key="index" @click="emitIndex(index)">{{item}}</div>
 </div>
</template>

<script>
export default {
 props: ['articles'],
 methods: {
 emitIndex(index) {
 this.$emit('onEmitIndex', index)
 }
 }
}
</script>

二、  $children / $parent

vue中组件通信的八种方式(值得收藏!)

上面这张图片是vue官方的解释,通过$parent和$children就可以访问组件的实例,拿到实例代表什么?代表可以访问此组件的所有方法和data。接下来就是怎么实现拿到指定组件的实例。

使用方法

// 父组件中
<template>
 <div class="hello_world">
 <div>{{msg}}</div>
 <com-a></com-a>
 <button @click="changeA">点击改变子组件值</button>
 </div>
</template>

<script>
import ComA from './test/comA.vue'
export default {
 name: 'HelloWorld',
 components: { ComA },
 data() {
 return {
  msg: 'Welcome'
 }
 },

 methods: {
 changeA() {
  // 获取到子组件A
  this.$children[0].messageA = 'this is new value'
 }
 }
}
</script>
// 子组件中
<template>
 <div class="com_a">
 <span>{{messageA}}</span>
 <p>获取父组件的值为: {{parentVal}}</p>
 </div>
</template>

<script>
export default {
 data() {
 return {
  messageA: 'this is old'
 }
 },
 computed:{
 parentVal(){
  return this.$parent.msg;
 }
 }
}
</script>

要注意边界情况,如在#app上拿$parent得到的是new Vue()的实例,在这实例上再拿$parent得到的是undefined,而在最底层的子组件拿$children是个空数组。也要注意得到$parent和$children的值不一样,$children 的值是数组,而$parent是个对象

总结
上面两种方式用于父子组件之间的通信, 而使用props进行父子组件通信更加普遍; 二者皆不能用于非父子组件之间的通信。

三、provide/ inject

概念:

provide/ inject 是vue2.2.0新增的api, 简单来说就是父组件中通过provide来提供变量, 然后再子组件中通过inject来注入变量。

注意: 这里不论子组件嵌套有多深, 只要调用了inject 那么就可以注入provide中的数据,而不局限于只能从当前父组件的props属性中回去数据

举例验证

接下来就用一个例子来验证上面的描述:

假设有三个组件: A.vue、B.vue、C.vue 其中 C是B的子组件,B是A的子组件

// A.vue

<template>
 <div>
	<comB></comB>
 </div>
</template>

<script>
 import comB from '../components/test/comB.vue'
 export default {
 name: "A",
 provide: {
  for: "demo"
 },
 components:{
  comB
 }
 }
</script>
// B.vue

<template>
 <div>
 {{demo}}
 <comC></comC>
 </div>
</template>

<script>
 import comC from '../components/test/comC.vue'
 export default {
 name: "B",
 inject: ['for'],
 data() {
  return {
  demo: this.for
  }
 },
 components: {
  comC
 }
 }
</script>
// C.vue
<template>
 <div>
 {{demo}}
 </div>
</template>

<script>
 export default {
 name: "C",
 inject: ['for'],
 data() {
  return {
  demo: this.for
  }
 }
 }
</script>

四、ref / refs

ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例,可以通过实例直接调用组件的方法或访问数据, 我们看一个ref 来访问组件的例子:

// 子组件 A.vue

export default {
 data () {
 return {
  name: 'Vue.js'
 }
 },
 methods: {
 sayHello () {
  console.log('hello')
 }
 }
}
// 父组件 app.vue

<template>
 <component-a ref="comA"></component-a>
</template>
<script>
 export default {
 mounted () {
  const comA = this.$refs.comA;
  console.log(comA.name); // Vue.js
  comA.sayHello(); // hello
 }
 }
</script>

五、eventBus

eventBus  又称为事件总线,在vue中可以使用它来作为沟通桥梁的概念, 就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件, 所以组件都可以通知其他组件。

eventBus也有不方便之处, 当项目较大,就容易造成难以维护的灾难

在Vue的项目中怎么使用eventBus来实现组件之间的数据通信呢?具体通过下面几个步骤

1. 初始化

首先需要创建一个事件总线并将其导出, 以便其他模块可以使用或者监听它.

// event-bus.js

import Vue from 'vue'
export const EventBus = new Vue()

2. 发送事件

假设你有两个组件: additionNum 和 showNum, 这两个组件可以是兄弟组件也可以是父子组件;这里我们以兄弟组件为例:

<template>
 <div>
  <show-num-com></show-num-com>
  <addition-num-com></addition-num-com>
 </div>
</template>

<script>
import showNumCom from './showNum.vue'
import additionNumCom from './additionNum.vue'
export default {
 components: { showNumCom, additionNumCom }
}
</script>
// addtionNum.vue 中发送事件

<template>
 <div>
  <button @click="additionHandle">+加法器</button>  
 </div>
</template>

<script>
import {EventBus} from './event-bus.js'
console.log(EventBus)
export default {
 data(){
  return{
   num:1
  }
 },

 methods:{
  additionHandle(){
   EventBus.$emit('addition', {
    num:this.num++
   })
  }
 }
}
</script>

3. 接收事件

// showNum.vue 中接收事件

<template>
 <div>计算和: {{count}}</div>
</template>

<script>
import { EventBus } from './event-bus.js'
export default {
 data() {
  return {
   count: 0
  }
 },

 mounted() {
  EventBus.$on('addition', param => {
   this.count = this.count + param.num;
  })
 }
}
</script>

这样就实现了在组件addtionNum.vue中点击相加按钮, 在showNum.vue中利用传递来的 num 展示求和的结果.

4. 移除事件监听者

如果想移除事件的监听, 可以像下面这样操作:

import { eventBus } from 'event-bus.js'
EventBus.$off('addition', {})

六、Vuex

1.  Vuex介绍

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化.

Vuex 解决了多个视图依赖于同一状态和来自不同视图的行为需要变更同一状态的问题,将开发者的精力聚焦于数据的更新而不是数据在组件之间的传递上

2. Vuex各个模块

  1. state:用于数据的存储,是store中的唯一数据源
  2. getters:如vue中的计算属性一样,基于state数据的二次包装,常用于数据的筛选和多个数据的相关性计算
  3. mutations:类似函数,改变state数据的唯一途径,且不能用于处理异步事件
  4. actions:类似于mutation,用于提交mutation来改变状态,而不直接变更状态,可以包含任意异步操作
  5. modules:类似于命名空间,用于项目中将各个模块的状态分开定义和操作,便于维护

3. Vuex实例应用

// 父组件

<template>
 <div id="app">
  <ChildA/>
  <ChildB/>
 </div>
</template>

<script>
 import ChildA from './components/ChildA' // 导入A组件
 import ChildB from './components/ChildB' // 导入B组件

 export default {
  name: 'App',
  components: {ChildA, ChildB} // 注册A、B组件
 }
</script>
// 子组件childA

<template>
 <div id="childA">
  <h1>我是A组件</h1>
  <button @click="transform">点我让B组件接收到数据</button>
  <p>因为你点了B,所以我的信息发生了变化:{{BMessage}}</p>
 </div>
</template>

<script>
 export default {
  data() {
   return {
    AMessage: 'Hello,B组件,我是A组件'
   }
  },
  computed: {
   BMessage() {
    // 这里存储从store里获取的B组件的数据
    return this.$store.state.BMsg
   }
  },
  methods: {
   transform() {
    // 触发receiveAMsg,将A组件的数据存放到store里去
    this.$store.commit('receiveAMsg', {
     AMsg: this.AMessage
    })
   }
  }
 }
</script>
// 子组件 childB

<template>
 <div id="childB">
  <h1>我是B组件</h1>
  <button @click="transform">点我让A组件接收到数据</button>
  <p>因为你点了A,所以我的信息发生了变化:{{AMessage}}</p>
 </div>
</template>

<script>
 export default {
  data() {
   return {
    BMessage: 'Hello,A组件,我是B组件'
   }
  },
  computed: {
   AMessage() {
    // 这里存储从store里获取的A组件的数据
    return this.$store.state.AMsg
   }
  },
  methods: {
   transform() {
    // 触发receiveBMsg,将B组件的数据存放到store里去
    this.$store.commit('receiveBMsg', {
     BMsg: this.BMessage
    })
   }
  }
 }
</script>

vuex的store,js

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
 // 初始化A和B组件的数据,等待获取
 AMsg: '',
 BMsg: ''
}

const mutations = {
 receiveAMsg(state, payload) {
  // 将A组件的数据存放于state
  state.AMsg = payload.AMsg
 },
 receiveBMsg(state, payload) {
  // 将B组件的数据存放于state
  state.BMsg = payload.BMsg
 }
}

export default new Vuex.Store({
 state,
 mutations
})

七、localStorage / sessionStorage

这种通信比较简单,缺点是数据和状态比较混乱,不太容易维护。

通过window.localStorage.getItem(key)获取数据

通过window.localStorage.setItem(key,value)存储数据

注意用JSON.parse() / JSON.stringify() 做数据格式转换
localStorage / sessionStorage可以结合vuex, 实现数据的持久保存,同时使用vuex解决数据和状态混乱问题.

八 $attrs与 $listeners

现在我们来讨论一种情况, 我们一开始给出的组件关系图中A组件与D组件是隔代关系, 那它们之前进行通信有哪些方式呢?

  1. 使用props绑定来进行一级一级的信息传递, 如果D组件中状态改变需要传递数据给A, 使用事件系统一级级往上传递
  2. 使用eventBus,这种情况下还是比较适合使用, 但是碰到多人合作开发时, 代码维护性较低, 可读性也低
  3. 使用Vuex来进行数据管理, 但是如果仅仅是传递数据, 而不做中间处理,使用Vuex处理感觉有点大材小用了.

在vue2.4中,为了解决该需求,引入了$attrs 和$listeners , 新增了inheritAttrs 选项。 在版本2.4以前,默认情况下,父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外),将会“回退”且作为普通的HTML特性应用在子组件的根元素上。接下来看一个跨级通信的例子:

// app.vue
// index.vue

<template>
 <div>
  <child-com1
   :name="name"
   :age="age"
   :gender="gender"
   :height="height"
   title="程序员成长指北"
  ></child-com1>
 </div>
</template>
<script>
const childCom1 = () => import("./childCom1.vue");
export default {
 components: { childCom1 },
 data() {
  return {
   name: "zhang",
   age: "18",
   gender: "女",
   height: "158"
  };
 }
};
</script>
// childCom1.vue

<template class="border">
 <div>
  <p>name: {{ name}}</p>
  <p>childCom1的$attrs: {{ $attrs }}</p>
  <child-com2 v-bind="$attrs"></child-com2>
 </div>
</template>
<script>
const childCom2 = () => import("./childCom2.vue");
export default {
 components: {
  childCom2
 },
 inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
 props: {
  name: String // name作为props属性绑定
 },
 created() {
  console.log(this.$attrs);
   // { "age": "18", "gender": "女", "height": "158", "title": "程序员成长指北" }
 }
};
</script>
// childCom2.vue

<template>
 <div class="border">
  <p>age: {{ age}}</p>
  <p>childCom2: {{ $attrs }}</p>
 </div>
</template>
<script>

export default {
 inheritAttrs: false,
 props: {
  age: String
 },
 created() {
  console.log(this.$attrs); 
  // { "gender": "女", "height": "158", "title": "程序员成长指北" }
 }
};
</script>

总结

常见使用场景可以分为三类:

  1. 父子组件通信: props; $parent / $children; provide / inject ; ref ;  $attrs / $listeners
  2. 兄弟组件通信: eventBus ;  vuex
  3. 跨级通信:  eventBus;Vuex;provide / inject 、$attrs / $listeners

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
javascript 弹出窗口中是否显示地址栏的实现代码
Apr 14 Javascript
了不起的node.js读书笔记之node的学习总结
Dec 22 Javascript
浅谈javascript中字符串String与数组Array
Dec 31 Javascript
Js数组排序函数sort()介绍
Jun 08 Javascript
理解javascript封装
Feb 23 Javascript
原生 JS Ajax,GET和POST 请求实例代码
Jun 08 Javascript
Angular懒加载机制刷新后无法回退的快速解决方法
Aug 30 Javascript
js中scrollTop()方法和scroll()方法用法示例
Oct 03 Javascript
一步一步的了解webpack4的splitChunk插件(小结)
Sep 17 Javascript
jQuery实现简单的Ajax调用功能示例
Feb 15 jQuery
D3.js的基础部分之数组的处理数组的排序和求值(v3版本)
May 09 Javascript
使用JavaScrip模拟实现仿京东搜索框功能
Oct 16 Javascript
Vue全局loading及错误提示的思路与实现
Aug 09 #Javascript
使用Vue CLI创建typescript项目的方法
Aug 09 #Javascript
详解vue beforeRouteEnter 异步获取数据给实例问题
Aug 09 #Javascript
微信小程序如何连接Java后台
Aug 08 #Javascript
vue 实现Web端的定位功能 获取经纬度
Aug 08 #Javascript
Vue-cli3.X使用px2 rem遇到的问题及解决方法
Aug 08 #Javascript
从零撸一个pc端vue的ui组件库( 计数器组件 )
Aug 08 #Javascript
You might like
PHP分页详细讲解(有实例)
2013/10/30 PHP
php集成环境xampp中apache无法启动问题解决方案
2014/11/18 PHP
php结合web uploader插件实现分片上传文件
2016/05/10 PHP
PHP实现的文件浏览器功能简单示例
2019/09/12 PHP
jQuery 常见操作实现方式和常用函数方法总结
2011/05/06 Javascript
基于jquery的web页面日期格式化插件
2011/11/15 Javascript
一个简单的js树形菜单
2011/12/09 Javascript
简单对比分析JavaScript中的apply,call与this的使用
2015/12/04 Javascript
jQuery使用zTree插件实现树形菜单和异步加载
2016/02/25 Javascript
javascript闭包概念简单解析(推荐)
2016/06/03 Javascript
jQuery对checkbox 复选框的全选全不选反选的操作
2016/08/09 Javascript
js HTML5上传示例代码完整版
2016/10/10 Javascript
NodeJS使用formidable实现文件上传
2016/10/27 NodeJs
vue 项目如何引入微信sdk接口的方法
2017/12/18 Javascript
微信小程序使用map组件实现获取定位城市天气或者指定城市天气数据功能
2019/01/22 Javascript
JS实现的全选、全不选及反选功能【案例】
2019/02/19 Javascript
JavaScript ES2019中的8个新特性详解
2019/02/20 Javascript
vue父组件触发事件改变子组件的值的方法实例详解
2019/05/07 Javascript
微信小程序录音实现功能并上传(使用node解析接收)
2020/02/26 Javascript
vue cli3适配所有端方案的实现
2020/04/13 Javascript
[50:02]完美世界DOTA2联赛循环赛 Magma vs IO BO2第一场 11.01
2020/11/02 DOTA
python实现dict版图遍历示例
2014/02/19 Python
Python中优化NumPy包使用性能的教程
2015/04/23 Python
python使用正则表达式来获取文件名的前缀方法
2018/10/21 Python
Python 脚本拉取 Docker 镜像问题
2019/11/10 Python
Python制作运行进度条的实现效果(代码运行不无聊)
2021/02/24 Python
师范毕业生自我鉴定
2014/01/15 职场文书
活动策划邀请函
2014/02/06 职场文书
2014年小学教学工作总结
2014/11/13 职场文书
2014年平安夜寄语
2014/12/08 职场文书
2014年机关党委工作总结
2014/12/11 职场文书
毕业生自荐信范文
2015/03/05 职场文书
飞屋环游记观后感
2015/06/08 职场文书
百年孤独读书笔记
2015/06/29 职场文书
想创业成功,需要掌握这些要点
2019/12/06 职场文书
解决jupyter notebook启动后没有token的坑
2021/04/24 Python