-
Notifications
You must be signed in to change notification settings - Fork 0
Description
在使用 vue 进行组件化开发的过程中,组件间的通信是一个避免不了的话题。其实仔细品读 vue 官方文档,对于这方面的介绍基本都是十分详细的。但是由于文档丰富繁杂,对于组件间通信的描述也是十分零散,需要仔细阅读。本文对笔者在开发实践过程中接触过的各种解决方案做个总结,希望读者可以在日常开发工作中有所帮助。
通过 Prop 向子组件传递数据
所有的 prop 都是父子组件单向下行绑定,父级 prop 的更新会向下流动到子组件中,但反之则不行。这种“单向数据流”会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
<!-- 子组件child.vue -->
<template>
<div class="container">
<p>{{title}}</p>
</div>
</template>
<script>
export default {
props: ['title']
}
</script>
<!-- 父组件<template>中 -->
<child title="我很快乐"></child>注意:在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变这个对象或数组本身将会影响到父组件的状态。
通过 $emit 向父组件传递数据
通常使用$emit 触发当前实例上的事件,可在子组件中使用它向父组件发消息。
当子组件需要改变某个 prop 时,则只需要通知父组件去改变即可。父组件监听自定义事件,通过回调的方式改变。
<!-- 子组件child.vue -->
<template>
<div class="container">
<p>{{title}}</p>
<button @click.native="$emit('changeTitle','我不快乐')">点击切换</button>
</div>
</template>
<script>
export default {
props: ['title']
}
</script>
<!-- 父组件中 -->
<template>
<child :title="title" @changeTitle="changeTitle"></child>
</template>
<script>
import Child from './child'
export default {
components: { Child },
data () {
return {
title: '我很快乐'
}
},
methods () {
changeTitle (title) {
this.title = title
}
}
}
</script>.sync
.sync 主要目的就是同步父子组件的数据,是一种语法糖的写法。实际上就是可在子组件中派发一个自定义事件update:(绑定.sync属性的名字)去改变属性。
当我们用一个对象同时设置多个 prop 的时候,也可以将这个 .sync 修饰符和 v-bind 配合使用。
<!-- 子组件child.vue -->
<template>
<div class="container">
<p>{{title}}</p>
<button @click="$emit('update:title','我不快乐')">点击切换</button>
</div>
</template>
<script>
export default {
props: ['title']
}
</script>
<!-- 父组件中 -->
<template>
<child :title.sync="title"></child>
</template>
<script>
import Child from './child'
export default {
components: { Child },
data () {
return {
title: '我很快乐'
}
}
}
</script>v-model
这种方式默认会利用名为 value 的 prop 和名为 input 的事件,也可以通过model选项来更改。
<!-- 子组件child.vue -->
<template>
<div class="container">
<p>{{value}}</p>
<button @click="$emit('input','我不快乐')">点击切换</button>
</div>
</template>
<script>
export default {
props: ['value'],
// 默认是value和input事件,可自定义
// model: {
// prop: 'title',
// event: 'changeTitle'
// }
}
</script>
<!-- 父组件中 -->
<template>
<child v-model="title"></child>
</template>
<script>
import Child from './child'
export default {
components: { Child },
data () {
return {
title: '我很快乐'
}
}
}
</script>注意: .sync和v-model的方式都不能和表达式一起使用 (例如 v-bind:title.sync=”doc.title + ‘!’” 是无效的)。取而代之的是,你只能提供你想要绑定的 property 名。
$attrs 和 $listeners
-
$attrs包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。
-
$listeners包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。
$root, $parent, $children
有时候可以利用组件间的父子关系去获取上面的属性和方法等。
-
$root是当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己。
-
$parent父实例,如果当前实例有的话。
-
$children当前实例的直接子组件。需要注意 $children 并不保证顺序,也不是响应式的。
ref 和 $refs
可以在原生标签上或组件上设置ref,这样可以通过this.$refs[ref的值]获取到组件的实例。如果是原生标签上的ref直接获取到页面元素,组件的化获取到的是组件实例。获取到组件实例当然就可以获取上面的属性和方法等,也不失为一种和子组件的通信方式。
<!-- 子组件child.vue -->
<template>
<div class="container">
<p>{{title}}</p>
<button @click="$emit('update:title','我不快乐')">点击切换</button>
</div>
</template>
<script>
export default {
props: ['title']
}
</script>
<!-- 父组件中 -->
<template>
<div ref="parent">
<child ref="child" :title.sync="title"></child>
</div>
</template>
<script>
import Child from './child'
export default {
components: { Child },
data () {
return {
title: '我很快乐'
}
},
mounted(){
console.log(this.$refs)
}
}
</script>依赖注入 provide / inject
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。
提示:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。
provide 和 inject 主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。
// 在父级中注入数据
provide() {
return { parentMsg: "爸爸" };
},
// 在任意子组件中可以注入父级数据
inject: ["parentMsg"] // 会将数据挂载在当前实例上中央事件总线 eventBus
首先创建一个中央事件总线,其实就是一句话搞定 export default new Vue(),导出一个vue的实例,在需要使用的组件里引入。然后在使用的地方直接import,然后在这个实例上面$emit某个自定义事件,在需要监听的地方也直接import,然后这个实例上面$on去监听执行回调。这种方式可以跨组件,兄弟组件间通信也可以。
一般是使用空的vue实例作为中央事件总线。当然也可以在实例里做一些事情,如下示例:
// eventBus进行监听,将数据直接放在Bus中
import Vue from 'vue'
const eventBus = new Vue({
data () {
return {
childVal: '哈哈'
}
},
created () {
// 将$on放在eventBus中完成
this.$on('change', value => {
this.childVal = value
})
}
})
export default eventBus然后在某个组件中去$emit事件,在另一个组件里面直接使用eventBus.childVal。
// 组件1中$emit事件
import eventBus from './eventBus'
export default {
methods: {
changeChildVal(){
eventBus.$emit('changeChildVal','嘻嘻')
}
}
}
// 组件2中直接在computed中使用eventBus.childVal
import eventBus from './eventBus'
export default {
computed: {
childVal () {
return eventBus.childVal
}
}
}vuex 状态管理
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
插槽
有时让插槽内容能够访问子组件中才有的数据是很有用的。
<!-- 子组件child.vue -->
<template>
<div class="container">
<p>{{title}}<p>
<slot :childText="childText"></slot>
</div>
</template>
<script>
export default {
props: ['title'],
data () {
return {
childText: '哈哈哈'
}
}
}
</script>
<!-- 父组件中 -->
<template>
<child :title.sync="title">
<template v-slot:default="slotProps">
{{ slotProps }}
</template>
</child>
</template>
<script>
import Child from './child'
export default {
components: { Child },
data () {
return {
title: '我很快乐'
}
}
}
</script>dispatch 和 broadcast
利用$parent, $children和$emit可以实现跨组件通信。实现如下:
function broadcast (componentName, eventName, params) {
this.$children.forEach(child => {
if(child.$option.name === componentName) {
child.$emit.apply(child, [eventName].concat(params))
} else {
broadcast.apply(child, [componentName, eventName].concat(params))
}
})
}
export default {
methods: {
// 通知指定组件
dispatch (componentName, eventName, params) {
let parent = this.$parent
let name = parent.$option.name
while(parent && (!name || name !== componentName)) {
parent = parent.$parent
if(parent) {
name = parent.$option.name
}
}
if(parent) {
parent.$emit.apply(parent, [eventName].concat(params))
}
},
// 广播
broadcast (componentName, eventName, params) {
broadcast.call(this, componentName, eventName, params)
}
}
}Vue.observable
让一个对象可响应。Vue 内部会用它来处理 data 函数返回的对象。
返回的对象可以直接用于渲染函数和计算属性内,并且会在发生变更时触发相应的更新。也可以作为最小化的跨组件状态存储器,用于简单的场景。
router 传参
有时候跳转页面切换组件时,可以借助router进行跨组件传参,但这种只适合传递简单的数据。
