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 相关文章推荐
js函数名与form表单元素同名冲突的问题
Mar 07 Javascript
Jsonp post 跨域方案
Jul 06 Javascript
javascript页面倒计时实例
Jul 25 Javascript
JS实现网页顶部向下滑出的全国城市切换导航效果
Aug 22 Javascript
JavaScript获取当前cpu使用率的方法
Dec 15 Javascript
分享两段简单的JS代码防止SQL注入
Apr 12 Javascript
js querySelector() 使用方法
Dec 21 Javascript
BootStrap Table实现server分页序号连续显示功能(当前页从上一页的结束序号开始)
Sep 12 Javascript
vue路由中前进后退的一些事儿
May 18 Javascript
vue解决花括号数据绑定不成功的问题
Oct 30 Javascript
JavaScript实现滑动门效果
Jan 18 Javascript
利用JS判断元素是否为数组的方法示例
Jan 08 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中var_dump,var_export,print_r三个函数的区别
2013/06/21 PHP
PHP垃圾回收机制引用计数器概念分析
2013/06/24 PHP
ThinkPHP3.2.3框架实现执行原生SQL语句的方法示例
2019/04/03 PHP
在Laravel中使用MongoDB的方法示例
2019/11/11 PHP
Javascript模块模式分析
2008/05/16 Javascript
写给想学习Javascript的朋友一点学习经验小结
2010/11/23 Javascript
jQuery 下拉列表 二级联动插件分享
2012/03/29 Javascript
各浏览器中querySelector和querySelectorAll的实现差异分析
2012/05/23 Javascript
extjs_02_grid显示本地数据、显示跨域数据
2014/06/23 Javascript
DOM基础教程之事件类型
2015/01/20 Javascript
js和jquery实现监听键盘事件示例代码
2020/06/24 Javascript
AngularJS初始化静态模板详解
2016/01/14 Javascript
jQuery焦点图轮播效果实现方法
2016/12/19 Javascript
jQuery实现二维码扫描功能
2017/01/09 Javascript
了解VUE的render函数的使用
2017/06/08 Javascript
JS实现按钮添加背景音乐示例代码
2017/10/17 Javascript
node puppeteer(headless chrome)实现网站登录
2018/05/09 Javascript
vue watch普通监听和深度监听实例详解(数组和对象)
2018/08/16 Javascript
VUE DOM加载后执行自定义事件的方法
2018/09/07 Javascript
JavaScript实现简单计算器
2020/03/19 Javascript
python2.7删除文件夹和删除文件代码实例
2013/12/18 Python
wxPython使用系统剪切板的方法
2015/06/16 Python
windows下安装Python和pip终极图文教程
2017/03/05 Python
使用pandas批量处理矢量化字符串的实例讲解
2018/07/10 Python
python操作文件的参数整理
2019/06/11 Python
Django获取该数据的上一条和下一条方法
2019/08/12 Python
python3通过qq邮箱发送邮件以及附件
2020/05/20 Python
BrandAlley英国:法国折扣奢侈品网上零售商
2017/07/03 全球购物
英国最大的宠物商店:Pets at Home
2019/04/17 全球购物
大二学期个人自我评价
2014/01/13 职场文书
师德师风自我剖析材料
2014/09/27 职场文书
幼儿园大班教师个人工作总结
2015/02/05 职场文书
健康证明
2015/06/19 职场文书
《语言的突破》读后感3篇
2019/12/12 职场文书
python实现MD5进行文件去重的示例代码
2021/07/09 Python
python编程实现清理微信重复缓存文件
2021/11/01 Python