Vue组件通信整理

前言

在我们Vue开发中,精髓就是组件,一个优秀的项目中,各种组件总是必不可少,我们暂且将组件分成三类:

  • 路由组件 由vue-router 生成每个页面的组件,承载着每个页面大部分的初始化内容以及业务逻辑;
  • 业务逻辑组件 在一个完整的项目中,经常会出现一些相同的业务逻辑的页面,此时将相同部分的逻辑抽出变成一个组件,在需要的页面倒入即可,这种组件减少了重复工作量,易于维护,但是只适用于当前项目;
  • 基础组件 不包含任何业务逻辑 只包含某种独立功能的基础组件,比如模态框、日历插件等,这种组件适用于大多数项目,通过高度抽象的API 配出处不通的功能。例如element-UI 和 IView库。

所以页面中的组件之间的关系可以如下图:
在这里插入图片描述

A与B 是父子关系 或者隔代关系。 B和C、B和D都是父子关系,C和D是兄弟关系。
因为组件与组件直接的复杂关系,我们每次组件直接的通信传值就变得尤其重要;在适合的项目中,使用适合的传值方式,可以让代码规范性可读性甚至性能上有了很大提高,那么下面我们来系统的讨论一下组件通讯传值的方式

组件与组件传值

我们首先大致罗列出组件直接通信的方式,然后再一一的详细分析:

1. 路由之间传值 将值拼接在路由后面,然后再新页面中 通过 $route.query.xxx方式获取
2. sessionStorage 和 localStorage
3. Vuex
4. 父传子 props
5. $attrs 自定义属性的形式 另外额外使用 v-bind 可以向更深层传递
6. provide/inject 可以 父传子 或者隔代相传
7. $emit + $on 事件传值
8. eventBus 事件传值
9. $ref获取
10. $parent $children

我们一个个的整理:

1、路由参数方式

首先在一个页面点击跳转到对应路由,并将参数拼接在路由后面

  this.$router.push(`/router-test?name=${this.name}&id=${this.id}`)

然后再进入新页面后,通过$route中的query 获取

created () {
    console.log(this.$route.query)
  }

显示内容如下~~
在这里插入图片描述

2、sessionStorage 和 localStorage

简单分析一下两者之间的区别:
localStorage生命周期是永久,这意味着除非用户显示在浏览器提供的UI上清除localStorage信息,否则这些信息将永远存在。
sessionStorage生命周期为当前窗口或标签页,一旦窗口或标签页被永久关闭了,那么所有通过sessionStorage存储的数据也就被清空了。
具体用法也很简单,就不具体用代码演示了,一个页面存数据,一个页面取数据~~

3、$refs

ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例;比如在父组件中的子组件实例上,定义一个ref

<template>
  <div class="hello">
    <h3 @click="test">测试$emit 和 $on 自我封装 $dispatch 与 $broadcast 函数</h3>
    <child ref="child"></child>
  </div>
</template>

<script>
import Child from './child'
import emit from '@/mixins/emit'
export default {
  mixins: [emit],
  name: 'HelloWorld',
  data () {
    return {
      msg: 'Welcome to Your Vue.js App',
      name: 'sea',
      id: 1232
    }
  },
  components: {
    Child
  },
  mounted () {
    console.log(this.$refs.child)
  },

然后我们在mounted中 打印 this.$refs.child 可以看出,可以取到子组件的data值。

4、 $parent 和 $children

在父组件中 打印 this.$children值为数组,因为一个父组件可以有很多个子组件。数组中的每一个值都是一个 VueComponent对象;
在子组件中,打印 this.$parent,其值依然是一个 VueComponent对象 可以通过 this.$parent.$parent…获取 祖辈对象.
同样,需要获取父、子组件的方法或者data值都是可以的。

5、 父传子 props

props是我们最常用的 父传子方式了,props可以定义数组或者对象,数组就是就是简单的值类型,而对象可以对props的每个属性自定义和设置默认值:

  • type: 类型 (String、Number、Boolean、Array、Object、Date、Function、Symbol、任何自定义构造函数、或上述内容组成的数组。会检查一个 prop 是否是给定的类型,否则抛出警告。)
  • default: 默认值,如果是对象或者数组 必须通过函数 return的方式(工厂模式)。
  • required: 定义prop是否为必填项
  • validator: 自定义检验规则
props: {
   childs: {
     type: Array,
     default: () => [1, 2, 3],
     required: true,
     validator: val => {
       return val.includes(3)
     }
   }
 },

6、 $emit + $on 事件传值

在子组件的 事件中,使用 $emit 定义自定义事件名和传值。
然后在父组件的子组件实例上,通过v-on监听 该子组件中的自定义事件的callback回调。

child.vue

<template>
 <div>
   <button @click="childClick">{{name}}</button>
 </div>
</template>
<script>
...
...
 methods: {
   childClick () {
     this.$emit('childClick', '我是child传入的值')
   }
 }
}
</script>

HelloWord.vue

<template>
 <div class="hello">
   <child @childClick="getChild"></child>
 </div>
</template>
 ...
 ...
<script>
import Child from './child'
 ...
 ...
 methods: {
   getChild (val) {
     console.log(val)
   }
 }
}
</script>

7、 $attr

这个方法的使用我就不再重复的去写了,大家可以看我的另一篇博客 Vue属性 inheritAttrs、$attrs和$listeners初窥

8、EventBus

EventBus 又称之为事件总线。实现的主要原理 我通俗的理解为:
借用Vue中的$emit 和 $on 两个实例方法(独立于整个Vue项目的Vue实例)。
基于这个简单的理解,我们可以将EventBus分为两种:局部 和 全局。

局部EventBus

我们建立一个单独的js 名字就命名为 event-bus.js,内容如下:

import Vue from 'vue'

export const EventBus = new Vue()

非常简单的两行,导出一个新的Vue实例命名为 EventBus,然后我们在哪里用就在那里引入:

// HelloWord.vue

<template>
  <div class="hello">
    <h3>测试$emit 和 $on 自我封装 $dispatch 与 $broadcast 函数</h3>
    <button @click="testEventBus">测试EventBus</button>
    <child></child>
  </div>
</template>
<script>
import Child from './child'
import { EventBus } from '@/mixins/event-bus'
export default {
  name: 'HelloWorld',
  components: {
    Child
  },
  methods: {
    testEventBus () {
      EventBus.$emit('addCount', { count: 1 })
    }
  }
}
</script>
<template>
  <div>
    <h3 style="color: #f80">我是child</h3>
  </div>
</template>
<script>
import { EventBus } from '@/mixins/event-bus'
export default {
  name: 'child',
  created () {
    EventBus.$on('addCount', obj => {
      console.log(obj)
    })
  },
  methods: {
    childClick () {
      this.$emit('childClick', '我是child传入的值')
      // this.dispatch('HelloWorld', 'event1', 'hello world')
    }
  }
}
</script>

这样我们就实现了组件之间数据通信。

另外,注销事件的方式:

EventBus.$off(‘eventName’, {})    移除xxx事件
EventBus.$off(‘eventName’)     移除xxx事件
EventBus.$off()              移除所有事件

全局EventBus

全局EventBus 比局部的方法更加优雅,但是因为是全局,定义在mian.js中,后期当代码量上来之后可能会导致混乱并不易维护。

// main.js
var EventBus = new Vue();

Object.defineProperties(Vue.prototype, {
    $bus: {
        get: function () {
            return EventBus
        }
    }
})

使用方式也非常简单,不需要再每个使用的组件中单独引用。我们把上面的例子稍稍改动一下就好:

// HelloWord.vue
methods: {
    testEventBus () {
      this.$bus.$emit('addCount', { count: 1 })
    }
  }

// Child.vue
this.$bus.$on('addCount', obj => {
      console.log(obj)
    })
9、provide/inject

provide和inject 这个两个方法总是同时出现,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。

官方认为 provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中

使用方法如下面示例(HelloWord.vue和child.vue是父子组件):

// HelloWord.vue

export default {
  name: 'HelloWorld',
  provide () {
    return {
      child: 'provide 传入子组件'
    }
  }
  
// child.vue
export default {
  name: 'child',
  inject: ['child'],
  mounted() {
  	console.log(this.child)   &nbsp;  // 打印 'provide 传入子组件'
  }
 }

另外provide和inject 还可以作为全局状态管理(一般大型项目还是推荐使用Vuex);我们来看一个demo:

// App.vue
export default {
  name: 'App',
  data () {
    return {
      userinfo: {}
    }
  },
  provide () {
    return {
      app: this
    }
  },
  created () {
    this.getUserinfo('wangyang')
  },
  methods: {
    getUserinfo (name) {
      this.userinfo = {
        name: name,
        age: 28,
        sex: 'man'
      }
    }
  }
}

一般我们的项目中,都会有一个全局使用的用户状态,一般以前我们都是使用 SessionStorage、Vuex去做管理,此时我们使用provide/reject一样可以。我们在最底层的组件App.vue中,设定provide中 app 为 App.vue中的this。当我们去其他组件中如果去调用和修改呢? 请看下面代码:

  // HelloWorld.vue
  
  inject: ['app'],
  mounted () {
    console.log(this.app.userinfo)  //  wangyang
    this.name = this.app.userinfo.name
  },
  methods: {
    editorUser () {
      this.app.getUserinfo('editor')
      this.name = this.app.userinfo.name  // editor
    }
  }

部分代码已省略~~
我们在HelloWorld中mounted生命周期函数值打印this.app.getUserinfo(‘editor’) 值为 wangyang。
然后我们点击页面中的按钮(事件editorUser)将新的name 传入App.vue中的userinfo修改方法getUserinfo。
然后再次打印,name值改变为 editor。

10、Vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。

在此引用一下官方的定义,我想大家写Vue项目,多少都用到了Vuex,在此我就不过多的介绍它的使用方法了,这也不是一句两句可以解释清楚的。贴出官方文档的连接跳转 Vuex文档。另外网上也有很多优秀的Vuex使用说明博客,有兴趣的童鞋自己去谷歌。

总结

在此列举的Vue组件通信方式并不能包含网上所有的方法。但是学以致用,在适合的项目中使用最好的方法才是我们需要追求的,另外搞懂Vue的整体运作,还需要更多的了解Vue的源码。

参考文章
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值