vue中组件通信方式
组件概念
组件是一段可复用的、利于维护的、方便组合的代码段。在 Vue 中组件实际就是一个 Vue 的实例。把一些相对独立的模块封装成组件非常有利于代码的复用以及维护。
vue是数据驱动视图更新的框架, 所以对于vue来说组件间的数据通信非常重要,那么组件之间如何进行数据通信的呢? 首先我们需要知道在vue中组件之间存在什么样的关系, 才更容易理解他们的通信方式, 就好像过年回家,坐着一屋子的陌生人,相互之间怎么称呼,这时就需要先知道自己和他们之间是什么样的关系。 vue组件中关系说明:
如上图所示, A与B、A与C、B与D、C与E组件之间是父子关系; B与C之间是兄弟关系;A与D、A与E之间是隔代关系; D与E是堂兄关系(非直系亲属) 针对以上关系我们归类为:
- 父子组件之间通信
- 非父子组件之间通信(兄弟组件、隔代关系组件等)
本文会介绍组件间通信的6种方式如下图目录所示:并介绍在不同的场景下如何选择有效方式实现的组件间通信方式。
一、props
/ $emit
父组件通过props
的方式向子组件传递数据,而通过$emit
子组件可以向父组件通信。
1. 父组件向子组件传值
下面通过一个例子说明父组件如何向子组件传递数据:在子组件children.vue
中如何获取父组件parent.vue
中的数据articles:['红楼梦', '西游记','三国演义']
// parent.vue 父组件
<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>
// 子组件 children.vue
<template>
<div>
<span v-for="(item, index) in articles" :key="index">{{item}}</span>
</div>
</template>
<script>
export default {
props: ['articles']
}
</script>
总结: props 只可以从上一级组件传递到下一级组件(父子组件),即所谓的单向数据流。而且 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
官方的解释,通过$parent
和$children
就可以访问组件的实例,拿到实例代表什么?代表可以访问此组件的所有方法和data
。为什么官方不推荐你使用者方法了。开销太大了,这两种方法就相当于原生JS种的document.querySelector(‘xxx’),直接获取元素的值。性能方面负担大。
使用方法
// 父组件中
<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>
要注意得到
$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 元素;如果用在子组件上,引用就指向组件实例,可以通过实例直接调用组件的方法或访问数据,$refs
是一个对象,持有当前组件中注册过 ref特性的所有 DOM 元素和子组件实例,我们看一个ref
来访问组件的例子:
// 子组件 children.vue
export default {
data () {
return {
name: 'Vue.js'
}
},
methods: {
sayHello () {
console.log('hello')
}
}
}
// 父组件 app.vue
<template>
<children ref="children"></children>
</template>
<script>
export default {
mounted () {
const comA = this.$refs.children;
console.log(comA.name); // Vue.js
comA.sayHello(); // hello
}
}
</script>
五、localStorage
/ sessionStorage
这种通信比较简单,缺点是数据和状态比较混乱,不太容易维护。 通过window.localStorage.getItem(key)
获取数据 通过window.localStorage.setItem(key,value)
存储数据
注意用
JSON.parse()
/JSON.stringify()
做数据格式转换localStorage
/sessionStorage
可以结合vuex
, 实现数据的持久保存,同时使用vuex解决数据和状态混乱问题.
六、 组件化和模块化的简单区别
模块化: 是从代码逻辑的角度进行划分的;方便代码分层开发,保证每个功能模块的职能单一。
- 主要是针对Javascript代码,隔离、组织复制的javascript代码,将它封装成一个个具有特定功能的的模块,有了模块,我们就可以更方便地使用别人的代码,想要什么模块就加载什么模块。
组件化: 是从UI界面的角度进行划分的;前端的组件化,方便UI组件的重用。
- 头部,内容区,弹出框甚至确认按钮都可以成为一个组件,每个组件有独立的HTML、css、js代码。我们可以把他放在页面的任意部分,也可以和其他组件一起组成新的组件,一个页面是各个组件的结合,可以根据需要进行组装。
总结
常见使用场景可以分为三类:
- 父子组件通信:
props
;$parent
/$children
;provide
/inject
;ref
- 兄弟组件通信: Vuex ;
- 跨级通信: Vuex;
provide
/inject