文章目录
- 什么是组件通信?
- 父子通信流程
- props
- Props 定义
- Props 作用
- 特点
- 数组写法
- 对象写法(props校验)
- 简写只验证数据类型:
- 完整写法,完整的验证:
- props父向子传值
- 用`props`父传子
- 在子组件中修改`props`
- $emit子向父传值
- 用`props`子传父
- 用`$emit`子传父
- 事件总线
- 事件总线前置知识
- `$on`
- `$off`
- 事件总线的使用
- 父通过ref、$refs向子拿值
- 父子通信`$children` /`$parent`
- $children
- 示例
- $parent
- 示例
- 注意事项
- 跨层级通信`provide/inject`
- 跨层级通信`attrs/listeners`
- vuex
什么是组件通信?
组件通信,就是指组件与组件之间的数据传递
- 组件的数据是独立的,无法直接访问其他组件的数据
- 想使用其他组件的数据,就需要组件通信
父子通信流程
- 父组件通过 props 将数据传递给子组件
- 子组件利用 $emit 通知父组件修改更新
props
Props 定义
组件上 注册的一些 自定义属性
Props 作用
-
向子组件传递数据
-
props接受的数据,只读,不可修改
-
props接收到的简单数据类型,不可修改
-
props接收到的复杂数据类型,可修改,vue也不会报错,但是不建议这样做
特点
- 可以 传递 任意数量 的prop
- 可以 传递 任意类型 的prop
数组写法
数组写法没有校验,所以不会在控制台报错
props: ['title','text', 'name']
对象写法(props校验)
作用:
- 为组件的 prop 指定验证要求,不符合要求,控制台就会有错误提示
- 帮助开发者,快速发现错误
简写只验证数据类型:
props: {
校验的属性名: 数据类型
}
完整写法,完整的验证:
props: {
校验的属性名: {
type: 数据类型, // Number String Boolean ...
required: true, // 是否必填
default: 默认值, // 默认值(未传值就使用默认值)
validator (value) { // value接收的就是传过来的值
// 自定义校验逻辑
return 布尔值 // 返回true就代表通过验证
}
}
}
示例:
<script>
export default {
// 完整写法(类型、默认值、非空、自定义校验)
props: {
w: {
type: Number,
//required: true,
default: 0,
validator(val) {
// console.log(val)
if (val >= 100 || val <= 0) {
console.error('传入的范围必须是0-100之间')
return false
} else {
return true
}
},
},
},
}
</script>
props父向子传值
用props
父传子
- 给子组件以添加属性的方式传值
- 子组件内部通过props接收
- 模板中直接使用 props接收的值
在子组件中修改props
- 如果传过来的是简单数据类型,那么是无法修改的
- 如果传过来的是复杂数据类型,那么是可以修改的,但是不建议去修改
- 子组件如果要修改父组件传过来的数据的话,可以用
$emit
去触发父组件的自定义事件,让父组件自己去修改数据
$emit子向父传值
用props
子传父
- 父通过自定义属性将自己的方法传送出去
- 子通过props接受父传过来的方法
- 子通过调用接收过来的方法去传参的形式传数据
- 因为方法是在父亲身上,父亲因此接收到了数据
用$emit
子传父
- 给子组件绑定自定义事件,事件触发时调用来自父亲的回调函数
vm.$emit( eventName, […args] )
,eventName
为字符串类型,[…args]
可省略,或者传入多条数据- 在子组件内利用
$emit
触发子组件绑定的自定义事件,传入数据,进行修改更新
上图流程:
- 点击
button
,调用回调函数changeTitle
- 回调函数调用
$emit
方法 $emit
方法会去触发子组件绑定的自定义事件changeTitle
,同时传入参数- 子组件绑定的自定义事件
changeTitle
被触发后会调用回调函数handleChange
- 回调函数
handleChange
用接收到的参数去修改数据msg
事件总线
事件总线前置知识
$on
可以给当前实例添加自定义事件,并且监听添加的自定义事件
vm.$on( event, callback )
- 在 Vue 2 中,
$on
是 Vue 实例的一个方法,监听当前实例上的自定义事件 - 事件可以由
vm.$emit
触发,当触发这些事件时,可以执行相应的回调函数 - 相比于在标签上直接写事件,通过
$on
添加的自定义事件灵活性更强
$off
用于移除自定义事件监听器
vm.$off( [event, callback] )
- 如果没有提供参数,则移除所有的事件监听器
- 如果只提供了事件,则移除该事件所有的监听器
- 如果同时提供了事件与回调,则只移除这个回调的监听器
事件总线的使用
事件总线可以用于任意两个组件之间的通信
1、在src目录下创建个utils文件夹,里面创建EventBus.js文件(事件总线)
EventBus.js
// 导入vue文件
import Vue from 'vue'
// 创建空vue实例对象
const Bus = new Vue()
// 导出空vue实例对象
export default Bus
2、接收方son1.vue
<template>
<div>
我是son1(接收消息)
</div>
</template>
<script>
import Bus from '../utils/EventBus'
export default {
name: 'mySon1',
created(){
Bus.$on('myEvent',(msg)=>{ // 通过$on为Bus自定义事件,当事件触发时,后面的回调函数接收信息
console.log('我是son1拿到了son2的数据:',msg)
})
}
}
</script>
3、发送方son2.vue
<template>
<div>
我是son2(发布消息)
<button @click="sendMsg">发送消息</button>
</div>
</template>
<script>
import Bus from '@/utils/EventBus'
export default {
name: 'mySon2',
data() {
return {
msg: '我是son2中的数据'
}
},
methods: {
sendMsg() {
Bus.$emit('myEvent', this.msg) // 通过$emit触发事件,触发事件的同时将数据传出去
}
}
}
</script>
Vue 的事件总线通常是通过将其定义在 Vue 的原型上来实现的,这样任何 Vue 组件都可以通过 this.$bus
来访问和使用它。这样做的好处是全局事件总线可以在任何组件内部进行事件的触发和监听,提供了一种在组件之间通信的机制
在 Vue 中定义全局事件总线的一般步骤是:
- 创建一个新的 Vue 实例作为事件总线。
- 将这个 Vue 实例挂载到 Vue 的原型上,通常使用
$bus
作为属性名。
示例如下:
// main.js 或其他入口文件
import Vue from 'vue';
import App from './App.vue';
// 创建一个新的 Vue 实例作为事件总线
const EventBus = new Vue();
// 将事件总线挂载到 Vue 的原型上
Vue.prototype.$bus = EventBus;
new Vue({
render: h => h(App),
}).$mount('#app');
之后,在任何一个 Vue 组件中,你都可以通过 this.$bus
来访问这个全局事件总线,然后使用 $emit
方法来触发事件,或者使用 $on
、$off
、$once
方法来监听、取消监听或一次性监听事件。
// 在某个组件中触发事件
this.$bus.$emit('event-name', payload);
// 在另一个组件中监听事件
this.$bus.$on('event-name', (payload) => {
// 处理事件逻辑
});
虽然全局事件总线提供了一种便捷的组件间通信方式,但它也有一些潜在的问题。过度使用全局事件总线可能导致组件之间的耦合度过高,使得代码难以理解和维护。因此,在使用全局事件总线时,需要权衡其便利性和潜在的风险,并考虑其他组件间通信的方式,如 props、事件、Vuex 等
父通过ref、$refs向子拿值
用ref
给子组件打标记,使用$refs
获取到的是vc组件(子组件)实例对象,拿到了vc实例对象了,我们就可以对子组件进行操作,拿取子组件中的值
son.vue
<template>
<div>
我是子组件
</div>
</template>
<script>
export default {
name: 'mySon',
data() {
return {
msg: '我是子组件中的数据'
}
}
}
</script>
App.vue
<template>
<div>
<son ref="son"></son>
</div>
</template>
<script>
import son from './components/son.vue';
export default {
components: { son },
mounted() {
console.log(this.$refs.son.msg) // 我是子组件中的数据
}
}
</script>
父子通信$children
/$parent
在 Vue 2 中,$children
和 $parent
是两个实例属性,它们允许你直接访问组件的父级或子级组件实例。然而,这种直接的父子通信方式通常不推荐用于复杂的组件结构,因为它们使组件之间的依赖关系变得隐式和难以维护。但是,在某些简单的场景下,它们可能是有用的。
$children
$children
是一个数组,包含当前组件实例的直接子组件。请注意,$children
并不保证顺序,也不是响应式的。
示例
// 父组件
export default {
mounted() {
console.log(this.$children); // 输出子组件的数组
}
}
// 子组件(可能有很多)
// ...
$parent
$parent
属性用来访问当前组件实例的父组件实例。
示例
// 子组件
export default {
mounted() {
console.log(this.$parent); // 输出父组件的实例
// 你可以通过 this.$parent 访问父组件的数据、方法等
}
}
注意事项
- 不推荐过度使用:过度依赖
$parent
和$children
会导致组件之间的耦合度过高,使得代码难以维护和理解。在复杂的项目中,应该优先使用 props 和 events 进行父子通信,或者使用 Vuex 进行状态管理。 - 非响应式:
$children
数组不是响应式的,也就是说,当子组件被添加或删除时,Vue 不会触发视图更新。因此,你不应该试图通过修改$children
来控制组件的渲染。 - 顺序不保证:
$children
数组中的组件顺序并不保证与它们在模板中的顺序一致。因此,你不应该依赖$children
数组的顺序来访问特定的子组件。 - 循环引用:在某些情况下,可能会出现父子组件之间循环引用的问题。这通常是由于不恰当的组件设计或错误的父子关系导致的。在这种情况下,使用
$parent
和$children
可能会导致不可预测的结果。 - 替代方案:对于父子组件之间的通信,推荐使用 props 和 events。对于跨组件通信,可以考虑使用 Vuex 或 EventBus(尽管 EventBus 在大型项目中也可能导致问题)。
跨层级通信provide/inject
provide
和 inject
主要在开发高阶插件/组件库时使用。它们允许一个祖先组件向其所有子孙组件提供一个依赖,不论组件层次有多深,该依赖都可以注入进来。
// 祖先组件
export default {
provide() {
return {
foo: 'foo'
};
},
// ...
}
// 子孙组件
export default {
inject: ['foo'],
created() {
console.log(this.foo); // "foo"
}
// ...
}
注意:provide
和 inject
主要是为高阶插件/组件库开发而设计的。在大多数应用中,应优先使用 props 和 events 进行父子组件通信,并且对于跨多层级的通信,Vuex 会是更好的选择
跨层级通信attrs/listeners
在 Vue 2 中,$attrs
和 $listeners
是两个特殊的属性,允许你在组件之间透传(passthrough)属性和事件监听器。
-
$attrs
包含了父组件中未被子组件声明的属性(attribute)。换句话说,它是一个对象,包含了父组件传递给子组件的所有属性,除了那些在子组件中已经通过props
选项明确声明的。 -
$listeners
包含了父组件传递给子组件的所有事件监听器(即v-on
绑定的事件)。
这两个特性在你需要创建封装组件或高阶组件时非常有用,因为它们允许你将额外的属性和事件监听器传递给子组件,而无需在封装组件中显式声明它们。
下面是一个简单的例子来说明 $attrs
和 $listeners
的用法:
<!-- ParentComponent.vue -->
<template>
<div>
<ChildComponent :prop1="value1" @event1="handleEvent1" class="extra-class" />
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
data() {
return {
value1: 'someValue'
};
},
methods: {
handleEvent1() {
console.log('Event 1 triggered');
}
}
};
</script>
<!-- ChildComponent.vue -->
<template>
<div>
<GrandChildComponent v-bind="$attrs" v-on="$listeners" />
</div>
</template>
<script>
import GrandChildComponent from './GrandChildComponent.vue';
export default {
components: {
GrandChildComponent
},
props: ['prop1'] // 假设 ChildComponent 只声明了一个 prop1
};
</script>
<!-- GrandChildComponent.vue -->
<template>
<div>
<!-- 这里可以访问通过 $attrs 传递过来的 class 和其他未声明的属性 -->
<!-- 也可以触发通过 $listeners 传递过来的事件 -->
</div>
</template>
<script>
export default {
mounted() {
// 假设我们要在这里触发 event1 事件
this.$emit('event1');
}
};
</script>
在上面的例子中,ParentComponent
传递了一个 prop1
属性,一个 event1
事件监听器,以及一个未声明的 class
属性给 ChildComponent
。在 ChildComponent
中,我们只声明了 prop1
作为 prop,因此 class
属性会被包含在 $attrs
中。同时,event1
事件监听器会被包含在 $listeners
中。
当我们使用 v-bind="$attrs"
和 v-on="$listeners"
将这些属性和事件监听器透传给 GrandChildComponent
时,GrandChildComponent
可以接收到这些属性和事件,就好像它们是直接被传递给它的一样。这样,GrandChildComponent
可以访问 class
属性和触发 event1
事件,尽管这些属性和事件是在 ParentComponent
中定义的。
vuex
Vuex 是一个插件,可以帮我们管理 Vue 通用的数据 (多组件共享的数据)