一、vue父子组件通信的方式 主要的通信方式如下所示:
1、prop(常用)——父组件传递数据给子组件
我们知道,vue的设计理念为 — — **单向数据流**,而所有的**prop**使得父子之间形成了一个**单向下行绑定**:父级prop的更新会向下流动到子组件,但要注意反过来是不行的— —这也意味着**不应该在子组件内部改变prop**,当然可以通过将父组件所传递过来的数值赋值给自定义变量然后对变量进行操作。形象的来说:父子组件之间的数据传递相当于**自上而下**的下水道管子,只能从上往下流,而不能逆流。在通常情况下,子组件只是单纯地接收父组件传来的数组,并随着父组件数据的更新而更新时会比较常用这种方式。
2、$emit——子组件与父组件通信
触发当前实例上的事件,附加参数都会传给监听器回调。这种方法是**子组件向父组件传递数值的方法**,它和prop正好相反,它是通过触发事件来通知父组件改变数据,从而达到改变子组件数据的目的。 具体用法为:this.$emit('监听事件名称', '传递的附加参数'); 在**子组件中**,满足某个条件时/某种情况下使用emit绑定相应的监听事件,然后在**父组件**引用的子组件中触发该绑定的监听事件,然后调用父组件下相关的函数实现相应的回调。
例子如下所示:
子组件:
<template>
<div @click="up"></div>
</template>
methods: {
up() {
this.$emit('upup','hehe'); //主动触发upup方法,'hehe'为向父组件传递的数据
}
}
父组件
<div>
<child @upup="change" :msg="msg"></child> //监听子组件触发的upup事件,然后调用change方法
</div>
methods: {
change(msg) {
this.msg = msg;
}
}
(**具体应用场景**:例如在一些列表页面中— — 添加与编辑操作为一个子组件,当完成该操作后,需要实现列表的刷新,这时候则可以在操作成功后子组件使用emit绑定监听事件,然后父组件通过该监听事件回调相应的获取列表/刷新列表的函数。)
3、.sync修饰符(语法糖,少用)
.sync修饰符在 vue@1.x 的时候曾作为双向绑定功能存在— —即子组件可以修改父组件中的值。但因为这违反了‘单向数据流’的设计理念,所以在vue@2.0被删除掉了,然而在 vue@2.3.0+ 以上版本又重新引入了这个 .sync 修饰符。但是这次它只是作为一个编译时的语法糖存在。简而言之就是它可以让我们手动进行更新父组件中的值,从而是数据改动更加的明显。
作为一个语法糖— —也就是某种写法的简写形式。具体如以下代码所示:
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event">
</text-document>
可以用.sync语法糖简写为:
<text-document v-bind:title.sync="doc.title"></text-document>
那么具体要如何做到双向绑定呢?也就是说要实现——改变子组件文本框的值的同时也改变父组件中的值,具体实现方法如以下例子代码所示:
<div id="app">
<Login :name.sync="userName"></Login> {{ userName }}
</div>
let Login = Vue.extend({
template: ` <div class="input-group">
<label>姓名:</label>
<input v-model="text">
</div> `,
props: ['name'],
data () { return { text: '' } },
watch: { text (newVal) { this.$emit('update:name', newVal) } }
});
new Vue({ el: '#app', data: { userName: '' }, components: { Login }})
当然重点在于这一段代码:this.$emit('update:name', newVal);
官方语法是:update:myPropName 其中 myPropName 表示要更新的 prop 值。
在子组件中监听值的改变并作出更新,然后在父组件引用的子组件中使用.sync语法糖实现‘双向绑定’。
当然,不使用语法糖而用.$emit也能达到同样的效果。
4、vuex状态管理——在复杂的项目中实现父子组件的通信会推荐使用该方法
vuex——专为vue.js应用程序开发的状态管理模式。
其中的运作模式简单来说就是:父组件通过vuex来进行中转,那么vuex就类似于一个仓库,父组件把东西放到仓库,然后子组件从仓库中取出东西。由于子组件是通过计算属性来获取仓库里面的值**(具有实时性),一旦父组件重新改变了值,子组件就会重新从vuex仓库中读取数据**。
例子:
父组件father.vue
<template>
<div>
<h4>父组件</h4>
<child></child>
<button @click="transmitData">通过vuex给子组件传值</button>
</div>
</template>
<script>
import Child from './child.vue'
export default {
components: {
Child
},
data() {
return {
testData: '我是父组件传递给子组件的值'
}
},
methods: {
transmitData() {
// 通过commit提交数据改变vuex里面的数据
this.$store.commit('fatherData', this.testData)
}
}
}
</script>
<style scoped>
</style>
子组件child.vue
<template>
<div>
<p v-if="_fatherData === null">暂无数据</p>
<p v-else>{{_fatherData}}</p>
</div>
</template>
<script>
export default {
computed:{
_fatherData(){
// 读取store里面的值
return this.$store.state.fatherDatas
}
}
}
</script>
<style scoped>
</style>
vuex仓库store.js
import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex) const store = new Vuex.Store({ // 初始化的数据 state: { fatherDatas: null }, // 改变state里面的值得方法 mutations: { fatherData(state, data) { state.fatherDatas = data } }})// 输出模块export default store
5、listeners(组件封装用的比较多)
官网对 **attrs" 传入内部组件——在创建高级别的组件时非常有用。
官网对 **listeners" 传入内部组件——在创建更高层次的组件时非常有用。
由以上解释可以看出,listeners就像是两个收纳箱,一个负责收纳属性,一个负责收纳事件,要注意的是它们都是以对象的形式来保存数据。
具体使用可参看以下代码:
<div id="app">
<Child
:foo="foo"
:bar="bar"
@one.native="triggerOne"
@two="triggerTwo">
</Child>
</div>
【注意】以上子组件中有两个属性和两个方法,但区别在于其中一个属性是prop声明,有一个事件为.native修饰器。
// 子组件
let Child = Vue.extend({
template: '<h2>{{ foo }}</h2>',
props: ['foo'],
created () {
console.log(this.$attrs, this.$listeners)
// -> {bar: "parent bar"}
// -> {two: fn}
// 这里我们访问父组件中的 `triggerTwo` 方法
this.$listeners.two()
// -> 'two'
}
})
// 父组件
new Vue({
el: '#app',
data: {
foo: 'parent foo',
bar: 'parent bar'
},
components: {
Child
},
methods: {
triggerOne () {
alert('one')
},
triggerTwo () {
alert('two')
}
}
})
从以上代码可以看到。父子组件可以通过listeners进行数据传递,由此可以方便地在需要的地方进行调用和处理。
6、provide 和 inject (高阶组件/组件库用的较多)
provide 和 inject是组合一起使用的,官方对他们的解释为:
provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。并且这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。
【注意】由祖先组件通过provide向子组件提供数据等的依赖,子组件利用inject来接收祖先组件所注入的**依赖。**具体代码:
<div id="app">
<Son></Son>
</div>
//子组件
let Son = Vue.extend({
template: '<h2>son</h2>',
// 接收提供/注入的依赖
inject: {
house: {
default: '没房'
},
car: {
default: '没车'
},
money: {
// 长大工作了虽然有点钱
// 但仅供生活费,其余需要来源于父母
default: '¥4500'
}
},
created () {
console.log(this.house, this.car, this.money)
// -> '房子', '车子', '¥10000'
}
})
new Vue({
el: '#app',
// 向子组件提供/注入依赖
provide: {
house: '房子',
car: '车子',
money: '¥10000'
},
components: {
Son
}
})
7、其他
除了以上几种通信方式,还有以下这些:
(1)EventBus(仅适用于极小型项目)
这种通信方式的主要思路为:声明一个全局VUE实例变量,把所有的通信数据、事件监听都存储到这个变量中以达到组件之间数据共享的目的,类似于vuex状态管理,但是这种方式只适用于小型项目,对于复杂度高的项目还是建议使用vuex。
<div id="app">
<child></child>
</div>
// 全局变量
let EventBus = new Vue()
// 子组件
let Child = Vue.extend({
template: '<h2>child</h2>',
created () {
console.log(EventBus.message)
// -> 'hello'
EventBus.$emit('received', 'from child')
}
})
// 父组件
new Vue({
el: '#app',
components: {
Child
},
created () {
// 变量保存
EventBus.message = 'hello'
// 事件监听
EventBus.$on('received', function (val) {
console.log('received: '+ val)
// -> 'received: from child'
})
}
})
(2)****$parent
如果当前存在父实例,可以通过访问父实例从而进行数据之间的交互,但极少情况下会直接修改父组件中的数据。
(3)****$root
当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己。通过访问根组件也能进行数据之间的交互,但极少情况下会直接修改父组件中的数据。
**(4)**broadcast / dispatch
它们为vue@1.0 中的方法,分别是事件广播 和 事件派发。虽然 vue@2.0 里面删掉了,但可以模拟这两个方法。可以借鉴 Element 实现。有时候还是非常有用的,比如在开发树形组件的时候等等。