组件系统
组件通讯是vue.js的核心之一,不管是在项目中,还是面试中,都是必考的知识点之一。组件通讯通常涉及到父子组件、兄弟组件、跨级组件的通讯。本文介绍组件间通信的几种常用方式,希望可以帮助大家更好的理解组件间的通信。
常用的几种组件通讯方式
- props/emit
- children
- provide/inject
- ref
- eventBus
- vuex
- localStorage和sessionStorage
- $attr
一、props/emit
父组件通过props将变量传递给子组件,子组件通过$emit监听方法中传递参数,在父组件中监听子组件传递的方法获取到参数。
1、父组件向子组件传值
// 父组件
<template>
<div>
<children-com :title="title"></children-com>
</div>
</template>
<script>
import childrenCom from './childrenCom.vue';
export default {
components: {
childrenCom
},
data() {
return {
title: '父组件传给子组件的值'
}
}
}
</script>
// 子组件
<template>
<div >
<span>父组件传过来的值---{{title}}</span>
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
default: ''
}
},
}
</script>
props只可以从上一级组件传递到下一级组件(父子组件),即所谓的单向数据流。而且props只读,不可被修改,所有修改会失效并警告。
2、子组件向父组件传值
$emit绑定一个事件,当执行时,将绑定的参数传递给父组件,父组件通过v-on监听并接收参数。在上面例子的基础上,点击button按钮,父组件显示子组件传递的值。
// 父组件
<template>
<div>
<children-com :title="title" @onEmitData="receptData"></children-com>
<p>{{curVal}}</p>
</div>
</template>
<script>
import childrenCom from './childrenCom.vue';
export default {
components: {
childrenCom
},
data() {
return {
title: '父组件传给子组件的值',
curVal: ''
}
},
methods: {
receptData(args) {
this.curVal = args;
}
}
}
</script>
// 子组件
<template>
<div >
<span>父组件传过来的值---{{title}}</span>
<Button @click="btnClick"></Button>
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
default: ''
}
},
methods: {
btnClick() {
this.$emit('onEmitData', '子组件给父组件传递的值')
}
}
}
</script>
二、parent
vue官方解释,通过children可以访问组件的实例,即意味着可访问此组件的所有方法和data数据。接下来通过案例来实现拿到指定组件的实例。
// 父组件
<template>
<div>
<children-com></children-com>
<Button @click="changeChild">按钮</Button>
</div>
</template>
<script>
import childrenCom from './childrenCom.vue';
export default {
components: {
childrenCom
},
data() {
return {
title: '父组件的值',
}
},
methods: {
changeChild() {
this.$children[0].messageA = '修改子组件的值';
}
}
}
</script>
// 子组件
<template>
<div >
<span>{{messageA}}</span>
<p>获取父组件的值---{{parentVal}}</p>
</div>
</template>
<script>
export default {
data() {
return {
messageA: '子组件原有的值'
}
},
computed: {
parentVal() {
return this.$parent.title;
}
}
}
</script>
注意:如果在#app上拿parent得到的是undefined,而在最底层的子组件拿children的值是数组,而$parent是个对象
总结:上面两种方式皆用于父子组件之间的通信,而使用props进行父子通讯更加普遍,二者皆不可用于非父子组件之间的通信。
三、provide/inject
概念:
provide/inject是vue 2.2.0新增的api,主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。
这对选项需要一起使用,简单来说,就是祖先组件向子孙后代注入一个依赖,不论层级有多深。组件组件通过provide来提供变量,然后在子组件中通过inject来注入变量。
案例:有三个组件,A.vue,B.vue,C.vue,其中B是A的子组件,C是B的子组件
// 父组件 A.vue
<template>
<div class="home">
<grand-son></grand-son>
</div>
</template>
<script>
import grandSon from '@/components/grandSon.vue'
export default {
components: {
grandSon
},
provide() {
return {
name: this.prop
}
},
data() {
return {
prop: '父组件'
}
}
}
</script>
// 子组件 B.vue
<template>
<div>
<p class="grand-color">
这是父组件传过来的----->{{name}}
</p>
<grandSonSon/>
</div>
</template>
<script>
import grandSonSon from './grandSonSon';
export default {
components: {
grandSonSon
},
inject: ['name'],
data() {
return {
}
}
}
</script>
// 孙子组件 C.vue
<template>
<div>
<p class="grand-color">
这是祖先传过来的-----{{name}}
</p>
</div>
</template>
<script>
export default {
inject: ['name'],
data() {
return {
}
}
}
</script>
四、ref/refs
ref:如果在普通的DOM元素上使用,引用指向的就是DOM元素;如果用在子组件上,引用就指向组件实例。可以通过实例直接调用组件的方法或访问数据。下面我们来看一个通过ref来访问组件的例子:
// 父组件
<template>
<div>
<children-com ref="comA"></children-com>
</div>
</template>
<script>
import childrenCom from './childrenCom.vue';
export default {
components: {
childrenCom
},
mounted() {
let comA = this.$refs.comA;
console.log(comA.name); // vue.js
comA.say(); // hello
}
}
</script>
// 子组件 childrenCom .vue
export default {
data() {
return {
name: 'vue.js'
}
},
methods: {
say() {
console.log('hello');
}
}
}
五、eventBus
eventBus又称为事件总线,在vue中可以使用它来作为沟通桥梁的概念。就像是所有组件公用相同的事件中心,可以向该中心注册发送事件或接收事件,所有组件都可以通知其他组件。
eventBus也有不便之处,当项目较大时,就容器造成难以维护的灾难。
在项目中使用eventBus来实现组件之间的数据通信,需要以下几个步骤;
1、初始化
首先要创建一个事件总线并将其导出,以便其他模块都可以使用或监听它
// event-bus.js
import Vue from 'vue';
export default new Vue();
2、发送事件
有两个组件,这两个组件可以是兄弟组件也可以是父子组件;
<template>
<div>
<first />
<second />
</div>
</template>
<script>
import first from '@/components/first';
import second from '@/components/second'
export default {
components: {
first,
second
}
}
</script>
//
<template>
<div>
<button @click="handleClick">加法器</button>
</div>
</template>
<script>
import bus from './event-bus';
export default {
data() {
return {
num: 1
}
},
methods: {
handleClick() {
bus.$emit('handleBrother', {num: this.num++});
}
},
}
</script>
3、接收事件
<template>
<div>
<span>计算和:{{count}}</span>
</div>
</template>
<script>
import bus from './event-bus.js';
export default {
data() {
return {
count: 0
}
},
mounted() {
bus.$on('handleBrother', (param) => {
this.count += param.num
})
}
}
</script>
这样就实现了在first.vue中点击相加按钮,在second.vue中利用传递的num展示求和的结果。
4、移除事件监听者
import bus from './event-bus.js';
bus.$off('handleBrother', {});
六、Vuex
1、Vuex介绍
Vuex是一个专为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex解决了==多个视图依赖于同一状态==。==来自不同视图的行为需要变更同一状态==的问题,将开发者的精力聚焦于数据的更新而不是数据在组件之间的传递上。
2、Vuex各个模块
- state:用于数据的存储,是store中的唯一数据源
- getters:如vue中的计算属性一样,基于state数据的二次包装,常用于数据的筛选和多个数据的相关性计算
- mutations:类似函数,改变state数据的唯一途径,且==不能用于处理异步事件==
- actions:类似于mutation,用于提交mutation来改变状态,而不是直接变更状态,可以包含任意异步操作
- modules:类似于命名空间,用于项目中将各个模块的状态分开定义和操作,便于维护
3、Vuex实例
毫无关系的两个组件first.vue、second.vue,在first.vue中改变store.js中数据,在second组件同会同步更新;
// first.vue
<template>
<div>
<button @click="handleClick">改变store数据</button>
</div>
</template>
<script>
export default {
methods: {
handleClick() {
this.$store.commit('changeMsgA', 'first组件改变的msgA数据');
}
}
}
</script>
// second.vue
<template>
<div>
<p>second.vue组件获取的store数据--{{$store.state.msgA}}</p>
</div>
</template>
// store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
msgA: ''
},
mutations: {
changeMsgA(state, val) {
state.msgA = val;
}
},
})
七、localStorage和sessionStorag
这两个方式是H5新增的用于本地存储,通信简单,但不容易维护。通过window.localStorage.getItem(key)获取数据,通过window.localStorage.setItem(key)存储数据
注意JSON.parse() / JSON.stringify()做数据格式转换
八、listeners
隔代组件之间的通信方式,通常有以下几种:
- 使用props绑定一级一级的信息传递,如果孙子组件的状态改变需要传给祖先组件,需使用事件系统一级级往上传递
- 使用eventBus,碰到多人合作时,代码的维护性较低,代码可读性低;
- 使用VueX来进行数据管理,但是如果项目中多个组件共享状态比较少,项目比较小,并且全局状态比较少,那使用VueX来实现该功能,有点大材小用。
基本概念
vue2.4中,为了解决该需求,引入了listeners。
inheritAttrs
默认情况下,子组件无法获取到props中未定义的值。通过将 inheritAttrs=true禁止这种默认行为,配合$attr获取到父作用域中所有的属性(除了props传递的属性以及Class或者Style)来进行父组件向子组件传值
vm.$attr
只读的VUE实例属性,包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外),并且可以通过v-binnd=$arrts传入内部组件中
vm.$listeners
包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过v-on="$listeners"传入内部组件——在创建更高层次的组件时非常有用。
接下来看一个跨级通信的例子:
// 父组件
<template>
<div>
<children :name="name" :age="age" :gender="gender" :height="height" @event1="goEvent1" @event2="goEvent2"></children>
</div>
</template>
<script>
import children from '@/components/children.vue'
export default {
components: {
children
},
data() {
return {
name: 'zhang',
age: '18',
gender: '男',
height: '189'
}
},
methods: {
goEvent1(val) {
console.log(val);
},
goEvent2(value) {
console.log(value);
}
}
}
</script>
// children.vue
<template>
<div>
<p>name:{{name}}</p>
<p>children的$attrs{{$attrs}}</p>
<button type="button" @click="submit">提交</button>
<grand-son v-bind="$attrs" v-on="$listeners"></grand-son>
<!-- 通过$listeners将父作用域中的v-on事件监听器,传入grandSon,使得grandSon可以获取到app中的事件 -->
</div>
</template>
<script>
import grandSon from './grandSon';
export default {
props: {
name: String
},
inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
components: {
grandSon
},
methods: {
submit() {
this.$emit('event1', 'children提交事件');
}
}
}
</script>
// grandSon.vue
<template>
<div>
<p class="grand-color">
age:{{age}}
</p>
<p>childCom2--$attrs:{{$attrs}}</p>
<input v-model="inputValue" name="" id="" @input="goInput">
</div>
</template>
<script>
export default {
props: {
age: String
},
data() {
return {
inputValue: ''
}
},
methods: {
goInput () {
this.$emit('event2', this.inputValue);
}
}
}
</script>
总结
组件通信常见使用场景可以分为三类:
- 父子组件通信:props/parent/attr/$listeners
- 兄弟组件通信:eventBus、Vuex
- 跨级通信:eventBus、Vuex、provide/inject、listeners