前言
在我们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) // 打印 '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的源码。