Vue组件通信整理
总结
常见使用场景可以分为三类:
父子组件通信: props
、 $parent
/ $children
、 provide
/ inject
、 ref
、 $attrs
/ $listeners
、$emit / on
兄弟组件通信: eventBus
、 vuex
跨级通信: eventBus
、vuex
、provide
/ inject
、$attrs
/ $listeners
Props
注意:HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名。
<!-- 父组件 -->
<children v-bind:props-id="id" ></children>
<!-- 父组件数据data -->
id:3
<!-- 子组件 -->
<!-- 方式一 -->
props:['propsId']
<!-- 方式二 -->
props:{
propsId:{
type:number,
requeired:true
}
}
$emit / on
详细介绍 https://www.cnblogs.com/Scooby/p/14775547.html
$children / $parent
注意 $children
并不保证顺序,也不是响应式的。在 3.x 中,$children property 已被移除,且不再支持。
<!-- 子组件 -->
children-one data {title:'Children 1'}
children-two data {title:'Children 2'}
children-three data {title:'Children 3'}
<!-- 父组件 -->
<children-one></children-one>
<children-two></children-two>
<children-three></children-three>
<!-- 父组件数据 -->
<!-- 特别注意:如果你需要访问子组件实例,我们建议使用 $refs。 -->
<!-- 特别注意:返回类型为Array<Vue instance> 。-->
this.$children //[object Object],[object Object],[object Object]
<!-- 子组件数据 可以获取到父组件的数据和方法 -->
this.$parent //[object Object]
节制地使用
$parent
和$children
- 它们的主要目的是作为访问组件的应急方法。更推荐用 props 和 events 实现父子组件通信。
特别注意边界情况。如在
#app
上拿$parent
得到的是new Vue()
的实例,在这实例上再拿$parent
得到的是undefined
,而在最底层的子组件拿$children
是个空数组。也要注意得到$parent
和$children
的值不一样,$children
的值是数组,而$parent
是个对象。
provide / inject
provide / inject是非响应式。
通常,当我们需要从父组件向子组件传递数据时,我们使用 props。想象一下这样的结构:有一些深度嵌套的组件,而深层的子组件只需要父组件的部分内容。在这种情况下,如果仍然将 prop 沿着组件链逐级传递下去,可能会很麻烦。
对于这种情况,我们可以使用一对 provide
和 inject
。无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。这个特性有两个部分:父组件有一个 provide
选项来提供数据,子组件有一个 inject
选项来开始使用这些数据。
provide链。当子孙组件上层父组件重复定义provide
提供的值。它的行为跟原型链是一致的——沿着链向上寻找,只要找到就停止。
注意:
provide
和inject
绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。既如果在祖辈组件里更改provide
的值,在孙子组件里是拿不到更新后的值。
/**
* 数据格式:
* provide:Object | () => Object
* inject:Array<string> | { [key: string]: string | Symbol | Object }
*
**/
//祖先组件
//该写法会报错,当需要传递是data中的数据时。
//Uncaught TypeError: Cannot read property 'color' of undefined 。因为访问不到Vue实例 。
provide: {
color: this.color,
}
//正确写法,但是此种写法依然不是可响应式的。
provide() {
return {
color: this.color,
};
}
/**
* 子孙组件
*inject用来指定一个数组或者一个对象,数组的话就放provide里字段的名称,而对象的话可以指定
* 当前实例中的字段名
* 对应provide里的字段名
* 默认值或者返回默认值的函数
*
**/
// 写法一
const Child = {
inject:['color']
}
// 写法二
const Child = {
inject: {
foo: {
from: 'color', //如果它需要从一个不同名字的 property 注入,则使用 from 来表示其源 property。
default: () => "默认值" //在 vue 2.5.0+ 的注入可以通过设置默认值使其变成可选项
}
}
}
provide / inject 可响应式
第一种方法:把值转为函数,记得要用箭头函数,不然不能正确获取this。
//祖先组件
data{ demoData:"red" }
provide() {
return {
test: () => {
return this.demoData;
},
};
}
//子孙组件
<template>
<div :style="{'color':test()}">测试数据{{test()}}</div>
</template>
第二种方法:把provide
所在的Vue实例给传递下去。
/**
* 推荐此种主流写法。
* 可以看到很多UI组件库就是通过这个方式来传递属性的,因为有可能在不确定层级的子组件里要获得祖组件里的值。
*
**/
//祖先组件
data{ demoData:"red" }
provide() {
return {
test: this,
};
}
//子孙组件
<template>
<div :style="{'color':test.demoData}">测试数据{{test.demoData}}</div>
</template>
函数式组件使用inject
由于函数式组件的一切都是通过context
来传递的,不能像普通组件一样来写inject
配置,所以对于函数式组件来说,要拿到provide
提供的值就要从context.injections
字段里去取。
ref / $refs
ref
被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs
对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例:
<!-- 如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素 -->
<p ref="p">hello</p>
<!-- 如果用在子组件上,引用就指向组件实例,可以通过实例直接调用组件的方法或访问数据 -->
<!-- child-component组件 -->
data{ test: "demoData" }
<!-- 父组件组件 -->
<child-component ref="child"></child-component>
this.$refs.child.test // demoData
当 v-for
用于元素或组件的时候,引用信息将是包含 DOM 节点或组件实例的数组。
关于 ref 注册时间的重要说明:因为 ref 本身是作为渲染结果被创建的,在初始渲染的时候你不能访问它们 - 它们还不存在!$refs
也不是响应式的,因此你不应该试图用它在模板中做数据绑定。
eventBus
在vue项目中,父子组件间的通讯很方便。但兄弟组件或多层嵌套组件间的通讯,就会比较麻烦。这时,使用eventBus通讯,就可以很便捷的解决这个问题。
eventBus可以在全局定义,实现全项目通讯,使用方法也很简单。
全局定义,可以将eventBus绑定到vue实例的原型上,也可以直接绑定到window对象上。
eventBus绑定到vue实例的原型
// eventName为自定义事件名称
// 初始化-全局定义。mian.js
Vue.prototype.$EventBus = new Vue();
// 触发事件
this.$EventBus.$emit('eventName', param1,param2,...)
// 监听事件
this.$EventBus.$on('eventName', (param1,param2,...)=>{
//需要执行的代码
})
// 移除事件
this.$EventBus.$off('eventName');
eventBus绑定到window对象
// eventName为自定义事件名称
// 初始化-全局定义。mian.js
window.eventBus = new Vue();
// 触发事件
EventBus.$emit('eventName', param1,param2,...)
// 监听事件
EventBus.$on('eventName', (param1,param2,...)=>{
//需要执行的代码
})
// 移除事件
EventBus.$off('eventName');
VUEX
详情文章链接 https://www.cnblogs.com/Scooby/p/15214335.html
$attrs / $listeners
多级组件嵌套需要传递数据时,通常使用的方法是通过vuex。如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,这就有点大材小用了。所以就有了 $attrs / $listeners ,通常配合 inheritAttrs 一起使用。
inheritAttrs:默认值为 true。
默认情况下父作用域的不被认作 props 的 attribute 绑定 (attribute bindings) 将会“回退”且作为普通的 HTML attribute 应用在子组件的根元素上。当撰写包裹一个目标元素或另一个组件的组件时,这可能不会总是符合预期行为。通过设置
inheritAttrs
到false
,这些默认行为将会被去掉。而通过 (同样是 2.4 新增的) 实例 property$attrs
可以让这些 attribute 生效,且可以通过v-bind
显性的绑定到非根元素上。查 看 官 网注意:这个选项不影响
class
和style
绑定。
感觉还是挺晦涩难懂的,简单的说就是 inheritAttrs:true 继承除props之外的所有属性;inheritAttrs:false 只继承class属性。
$attrs:包含了父作用域中不被认为 (且不预期为) props 的特性绑定 (class 和 style 除外),并且可以通过 v-bind=”$attrs” 传入内部组件。当一个组件没有声明任何 props 时,它包含所有父作用域的绑定 (class 和 style 除外)。
$listeners:包含了父作用域中的 (不含 .native 修饰符) v-on 事件监听器。它可以通过 v-on=”$listeners” 传入内部组件。它是一个对象,里面包含了作用在这个组件上的所有事件监听器,相当于子组件继承了父组件的事件。
祖先组件
<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>
父组件:
<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() {
// 结果:age, infoObj, 因为父组件共传来name, age, infoObj三个值,由于name被 props接收了,所以只有age, infoObj属性
console.log(this.$attrs);
console.log(this.$listeners); // updateInfo: f, delInfo: f
},
methods: {
addInfo () {
console.log('add info')
}
}
}
</script>
子组件:
<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>
参考链接
vue中8种组件通信方式: https://juejin.cn/post/6844903887162310669
eventBus详细原文链接: https://blog.csdn.net/qq_26834399/article/details/106387585
vue中的$attrs和$listeners: https://www.cnblogs.com/dhui/p/12931953.html
provide/inject详情文章链接: https://zhuanlan.zhihu.com/p/184967263