1. 父 => 子通信:pros;
2. 子 => 父通信: $emit; $parent / $children; $ref 也可以;
3. 兄弟通信:$Bus; Vuex;
4. 跨级通信: $Bus; Vuex; provide / inject ; $attrs / $listeners
1.父组件 传值给 子组件
1.1.props
父组件通过属性的方式向子组件传值,子组件通过 props 来接收。
// 父组件
<user-detail :name="name" /> <!--通过属性 :myName="name" 传值-->
export default {
components: {
UserDetail
}
......
}
在子组件中使用props(可以是数组也可以是对象)接收即可。可以传多个属性。
// 子组件
export default {
props: ['name']
}
/*
props: { name: String } //这样指定传入的类型,如果类型不对会警告
props: { name: [String, Number] } // 多个可能的类型
prosp: { name: { type: String, requires: true } } //必填的的字符串
props: {
name: {
type: Array,
default: () => [] // default指定默认值
}
}
*/
2.子组件 传值给 父组件
2.1.自定义事件 $emit()
在父组件中绑定一个事件(自定义事件),通过 this.$emit() 在子组件中触发
通过函数返回数值
// 子组件
<button @click="testName">改变父组件的name</button>
export default {
methods: {
//子组件的事件
testName() {
this.$emit('Test', this.name) // 通过this.$emit触发父组件中test事件,并传参this.name (可以携带多个值,和多种数据类型)
// 注:此处事件名称与父组件中绑定的事件名称要一致
}
}
}
// 父组件
<child @Test="Test"></child>//绑定自定义事件 test
methods: {
Test(name) { // name形参是子组件中传入的值Jack
this.name = name
}
}
2.2. 通过 callback 函数
// 父组件
<child :callback="callback"></child>
methods: {
callback: function(name) {
this.name = name
}
}
// 子组件
<button @click="callback('Jack')">改变父组件的name</button>
props: {
callback: Function,
}
2.3. $attrs 和 $listeners
多级组件嵌套需要传递数据时,通常使用的方法是通过vuex。如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,这就有点大材小用了。所以就有了 $attrs / $listeners ,通常配合 inheritAttrs 一起使用。
$attrs:包含了父作用域中不被认为 (且不预期为) props 的特性绑定 (class 和 style 除外),并且可以通过 v-bind=”$attrs” 传入内部组件。当一个组件没有声明任何 props 时,它包含所有父作用域的绑定 (class 和 style 除外)。
$listeners:包含了父作用域中的 (不含 .native 修饰符) v-on 事件监听器。它可以通过 v-on=”$listeners” 传入内部组件。它是一个对象,里面包含了作用在这个组件上的所有事件监听器,相当于子组件继承了父组件的事件。
//father.vue 组件:
<template>
<child :name="name" :age="age" :infoObj="infoObj" @updateInfo="updateInfo" @delInfo="delInfo" />
</template>
<script>
import Child from '../components/child.vue'
export default {
name: 'father',
components: { Child },
data () {
return {
name: 'Lily',
age: 22,
infoObj: {
from: '上海',
job: 'policeman',
hobby: ['reading', 'writing', 'skating']
}
}
},
methods: {
updateInfo() {
console.log('update info');
},
delInfo() {
console.log('delete info');
}
}
}
</script>
//father.vue 组件:
<template>
<child :name="name" :age="age" :infoObj="infoObj" @updateInfo="updateInfo" @delInfo="delInfo" />
</template>
<script>
import Child from '../components/child.vue'
export default {
name: 'father',
components: { Child },
data () {
return {
name: 'Lily',
age: 22,
infoObj: {
from: '上海',
job: 'policeman',
hobby: ['reading', 'writing', 'skating']
}
}
},
methods: {
updateInfo() {
console.log('update info');
},
delInfo() {
console.log('delete info');
}
}
}
</script>
child.vue 组件:
<template>
<grand-son :height="height" :weight="weight" @addInfo="addInfo" v-bind="$attrs" v-on="$listeners" />
// 通过 $listeners 将父作用域中的事件,传入 grandSon 组件,使其可以获取到 father 中的事件
</template>
<script>
import GrandSon from '../components/grandSon.vue'
export default {
name: 'child',
components: { GrandSon },
props: ['name'],
data() {
return {
height: '180cm',
weight: '70kg'
};
},
created() {
console.log(this.$attrs);
// 结果:age, infoObj, 因为父组件共传来name, age, infoObj三个值,由于name被 props接收了,所以只有age, infoObj属性
console.log(this.$listeners); // updateInfo: f, delInfo: f
},
methods: {
addInfo () {
console.log('add info')
}
}
}
</script>
//grandSon.vue 组件:
<template>
<div>
{{ $attrs }} --- {{ $listeners }}
<div>
</template>
<script>
export default {
... ...
props: ['weight'],
created() {
console.log(this.$attrs); // age, infoObj, height
console.log(this.$listeners) // updateInfo: f, delInfo: f, addInfo: f
this.$emit('updateInfo') // 可以触发 father 组件中的updateInfo函数
}
}
</script>
3.兄弟组件间传值
3.1.全局事件总线
如果要了解其原理,需要先了解JavaScript原型链
在 main.js 中全局安装事件总线
因为只有组件对象或者Vue的实例化对象才能调用$on和$emit
想要成为事件总线的条件: (1) 所有的组件对象必须都能看得到这个总线对象,因此我们把这个对象放在了Vue原型 (2) 这个事件总线对象必须能调用$on和$emit方法(总线对象必须是Vue的实例化对象或者是组件对象)
//创建实例
new Vue({
el: '#app',
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this //安装事件总线
}
})
A组件 ==传值==> B组件 为例
//A 组件,触发
this.$bus.$emit('getName',this.xxx)
// B 组件,绑定
mounted(){
this.$bus.$on('getName',this.name)
},
3.2.通过 $emit (自定义事件) 和 props 结合的方式
也就是,找一个父组件或子组件,作为桥梁(或者叫跳板),实现兄弟组件间通讯;但使用次数多的情况下,不推荐使用;
具体方法,请看本文 1.1 和 2.1 章节。
3.3.Vuex
4. 多层父子组件通信
4.1.provide/inject
祖父和孙子通信,或者是跨越了更多层级的父子组件,这种情况不可能由子组件一级一级的向上传递参数,特别是在组件层级比较深,嵌套比较多的情况下,会导致代码很混乱。
provide/inject:
父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量,不管组件层级有多深,在父组件生效的生命周期内,这个变量就一直有效。
//父组件
export default {
provide: { // 它的作用就是将 **name** 这个变量提供给它的所有子组件。
name: this.name
}
}
//子组件
export default {
inject: ['name'], // 注入了从父组件中提供的name变量
mounted () {
console.log(this.name);
}
}
注意:provide & inject 的绑定不是响应式。父组件的name发生变化,子组件不会随之变化。
实现 provide 和 inject 数据响应
provide 祖先组件的实例,然后在子孙组件中注入依赖,这样就可以在后代组件中直接修改祖先组件的实例的属性,不过这种方法有个缺点就是这个实例上挂载很多没有必要的东西,如:props,methods;
// 父组件
<div>
<button @click="changeName">修改姓名</button>
</div>
<script>
......
data() {
return {
name: "奥利给"
};
},
provide() {
return {
parentObj: this //提供祖先组件的实例
};
},
methods: {
changeName() {
this.name = '哈拉少'
}
}
</script>
//后代组件
<template>
<div class="border2">
<P>姓名:{{parentObj.name}}</P>
</div>
</template>
<script>
export default {
inject: {
parentObj: {
default: () => ({})
}
} // 或者inject: ['parentObj']
};
</script>