基本概念
Vue 作为一个渐进式 JavaScript 框架,其核心设计之一就是组件化,它通过提供多种隔离手段来确保组件的独立性和封装性(如CSS样式隔离的 '<style scoped>' ,数据隔离data函数),增强代码的复用性、可维护性增强、开发效率提升。
但随着项目复杂度的增加,组件与组件之间不可避免地需要进行数据交互。为了在实现目的的同时,减少组件之间的直接依赖和耦合,vue提供了许多组件通信的方式。
vue 2组件通信
传递响应式数据提供的一个可观察的对象(如用Vue.observable()创建的对象)
1.props
props通信方法可以实现父组件向子组件传递参数,也可以实现子组件给父组件传递参数,是使用频率最高的
1)父传子:属性值非函数
父组件,引入子组件并给子组件绑定一个message参数,用以传递父组件的数据
子组件,使用props()接收
代码:
//父组件传递
<ChildA :message="parent_msg"/>
//子组件接收
export default {
//数组写法
props: ["message"]
//对象写法
props :{
message: {
type: String,
required: true, //是否规定必须得有
default: '默认值'//默认数据,当父组件没有传递数据时,读取该数据
}
}
}
2)子传父:属性值为函数(可携带值)
父组件,引入子组件并给子组件绑定一个sendMsg参数(可以携带参数的函数)
子组件,使用props()接收,接收到函数后,可用来向父组件传递自身数据
代码:
//父组件绑定带值函数
<ChildA :sendMsg="receive"/>
receive(value){
console.log(value)//子组件数据
}
//子组件接收传递
<button @click="sendMsg('子组件数据')">传递</button>
props: ["sendMsg"]
//或
props:{
sendMsg:{
type:Function,
default:()=>{}
}
}
2.$emit
自定义事件emits,常用于子传父
父组件:引入子组件,并监听子组件的自定义事件sendMsg的触发,当事件被触发时,执行方法receive来处理接收到的数据
子组件:使用this.$emit声明一个自定义事件sendMsg,调用tirggerFun方法时触发该事件并向外传递数据child_msg
代码:
//父组件监听事件触发方法
<ChildA @sendMsg="receive"/>
receive(value){
console.log(value)//子组件数据
}
//子组件,触发事件携带值
<button @click="triggerFun">传递</button>
triggerFun(){
this.$emit('sendMsg','子组件数据')
}
3.provide和inject
可以实现上层组件对于后代组件的值传递,相当于 “ 穿透传递 ”
祖组件,引入子组件,使用provide向后代组件提供数据。
后代组件,使用inject接收数据。
注意:provide是一个对象,inject是一个数组。
代码:
//祖组件
provide:{
parent_msg:'parent组件的信息'
}
//后代组件
inject:['parent_msg']
4.$refs
$refs是一个包含了所有带有 ref
属性的子组件或 DOM 元素的引用的对象
在父组件中通过$refs获取到子组件实例对象的引用后。可利用函数可以携带值得特性,由父组件向子组件传递数据,也可以由父组件主动获取子组件的数据(getter)
父传子
父组件:引入子组件,并在组件上为其声明一个ref属性childRef。通过this.$refs.childRef.[方法/属性]来访问子组件中的属性方法。
子组件:定义用于传递或接收数据的方法。如getValue
代码:
//父组件
<ChildA ref="childRef"/>
mounted() {
this.$refs.childRef.getValue('父组件数据')
}
//子组件
getValue(value){
console.log(value)
}
5.$parent和$children
1)$parent
$parent
:获取父组件实例对象,包含父节点中所有的数据和方法等
父组件:引入子组件
子组件:通过this.$parent.[方法/属性]来访问父组件中的属性方法。
代码:
//父组件
...属性方法
//子组件
console.log(this.$parent.parent_msg)//访问父组件属性
this.$parent.testFun()//调用父组件方法
2).$children
$children
:获取一个包含所有子组件(不包含孙子组件)的组件实例 对象数组,可以直接拿到子组件中所有数据和方法等
父组件:引入子组件,通过this.$children[index].[方法/属性]来访问子组件中的属性方法
//父组件
console.log(this.$children[0].child_msg)//访问第一个子组件的属性
this.$children[0].testFun()//调用第一个子组件的方法
//子组件
...属性方法
6.$attrs和$listeners
先理解:一个非 prop 的 attribute 是指传向一个组件,但是该组件并没有相应 prop 定义的 attribute。
//父组件
<ChildA :parent_msg="parent_msg" :other="othre"/>
//子组件
props:['parent_msg']
比如:这里的other就是非 prop 的 attribute
1)$attrs
$attrs:是一个包含父作用域里(除 class 和 style 之外)的非 props 属性集合,可以在模板的表达式中直接用 $attrs
访问这些非 prop 的 attribute
父组件:引入子组件,并在组件上绑定参数。如parent_msg和other_msg
子组件:直接在template中使用$attrs访问非props数据,在script中使用this.$attrs访问
代码:
//父组件
<ChildA :parent_msg="parent_msg" :other="other"/>
//子组件
1.在template中访问
{{ $attrs }}
2.在script中访问
console.log(this.$attrs)
3.通过v-bind直接传给下一级子组件:比如在子组件Child中给孙组件GrandSon绑定$atter
<GrandSon v-bind="$attrs"></GrandSon>
2)$listeners
$listeners:是一个包含父作用域里 .native 除外的监听事件集合。
父组件:引入子组件,并在组件上监听事件。如click和enter
子组件:在script中通过访问this.$listeners访问父组件中未被.native修饰的事件。可通过$emit来触发
代码:
//父组件
<ChildA @click="clickFun" @enter.native="enterFun"/>
//子组件
1.script中直接访问
console.log(this.$listeners) //{click: f}
this.$emit('click')//触发parent的clickFun
2.通过v-on直接传给下一级子组件:比如在子组件Child中监听孙组件GrandSon的事件
<GrandSon v-on="$listeners"></GrandSon>
7.v - model
父组件,引入子组件,并使用v-model指令为其绑定message参数用以数据的双向绑定
子组件,使用props()接收message;使用$emit触发mole事件修改父组件中的message
代码:
//父组件
<ChildA v-model="message"></ChildA>
</script>
//子组件
<input :value="message" @input="handlerChange" />
props: ["message"],
model:{ // 可以修改事件名,默认为 input
event:'update'
},
methods: {
handlerChange(e) {
this.$emit("update", e.target.value);
}
8.Bus
在 Vue 2 中,全局事件总线是一种在组件之间通信的方式,尤其是当组件结构较深,而你又不想通过多层组件的 props 和 events 传递数据时。全局事件总线通常是一个空的 Vue 实例,用于触发和监听事件。这样,任何组件都可以通过这个实例来发送或接收事件,实现跨组件通信。
1).在main.js文件中配置
// 创建一个空的 Vue 实例作为全局事件总线
const EventBus = new Vue();
// 将 EventBus 添加到 Vue 的原型上,这样你就可以在任何组件中通过 this.$eventBus 访问它
Vue.prototype.$eventBus = EventBus;
// Vue 应用实例
new Vue({
// ...
});
2).发送事件
在任意组件中,可以使用 $emit
方法来触发一个事件,并通过第二个参数传递数据。
// 在某个组件中
this.$eventBus.$emit('eventName', { message: 'Hello from Component A!' });
3).监听事件
在另一个组件中,可以使用 $on
方法来监听这个事件。同时,还需要在组件销毁前使用 $off
方法来移除监听器,以避免潜在的内存泄漏。
// 在另一个组件中
created() {
// 监听事件
this.$eventBus.$on('eventName', this.handleEvent);
},
beforeDestroy() {
// 移除监听器
this.$eventBus.$off('eventName', this.handleEvent);
},
methods: {
handleEvent(data) {
console.log(data.message); // 输出: Hello from Component A!
}
}
示例:
父组件,在Parent组件中引入ChildA,ChildB
子组件,在ChildA中注册监听事件,在ChildB中中触发ChildA注册的监听事件来实现数据通信
代码:
//Parent
....引入子组件
//ChildA
this.$bus.$on("testFun", (value) => {
console.log(value)
})
//ChildB
<button @click="triggerFun">触发</button>
triggerFun(){
this.$bus.$emit("testFun", '实现任意组件通信')
}
9.vuex
推荐参考:Vue状态管理工具:vuex
可创建一个专用于组件通信的模块
vue 3组件通信
以下示例皆以vue 3 Composition API 的 <script setup> 语法糖为基础(传递响应式数据使用ref,reactive声明的对象)
1.props
1)父传子:属性值非函数
父组件,引入子组件,并给其绑定一个参数
子组件,使用defineProps()接收
代码:
//父组件
<Child :message="info"></Child>
//子组件
const props = defineProps(['message'])
//或
const props = defineProps({
msg: {
type: String,
required: true, //是否规定必须得有
default: '默认值'//默认数据,当父组件没有传递数据时,读取该数据
}
}
defineProps 是 Vue 3 中 <script setup> 语法糖提供的一个编译时宏,用于在 <script setup> 语法糖中显式地声明组件接收的 props。
2)子传父:属性值为函数(可携带值)
父组件,引入子组件,并给其绑定一个参数(可以携带数据的函数)
子组件,使用defineProps()接收,在接收到父组件传递的函数后,可用来向父组件传递自身数据
代码:略
2.$emit
自定义事件emits,常用于子传父
父组件:引入子组件,并监听子组件的自定义事件sendMsg的触发,当事件被触发时,执行方法receive来处理接收到的数据
子组件:使用defineEmits声明一个可以传递数据的自定义事件getMsg,用以向父组件传递数据
代码:
//父组件
<Child @sendMsg="receive" />
function receive(value) {
console.log(value)
}
//子组件
<button @click="triggerFun()">传递</button>
const emits = defineEmits(['sendMsg'])
function triggerFun() {
emits('sendMsg', '子组件数据')
}
defineEmits是 Vue 3 中 <script setup> 语法糖提供的一个编译时宏,用于声明组件可以触发的自定义事件。当使用defineEmits时,实际上是在告诉Vue:“这个组件可能会触发这些事件,请帮我设置好内部机制以便我可以从组件内部触发它们。”
3.provide和inject
1)祖辈向后代传递数据
祖组件,从vue中引入provide ,而后使用provide向后代提供数据。provide('名字',值)
后代组件,从vue中引入inject,而后使用inject接收数据。inject('名字','默认值')
代码:
//parent组件
import { provide } from 'vue'
let parent_msg = 'parent的数据'
provide('dataName', parent_msg)
//grandson组件
import { inject } from 'vue'
const data = inject('dataName', '默认值')
2)后代向祖辈传递数据
祖组件,从vue中引入provide ,使用provide向后代提供可携带值得方法。provide('名字',方法)
后代组件,从vue中引入inject,使用inject接收方法,收到之后使用该方法携带数据向祖组件传递。inject('名字','默认方法')
代码:略
4.expose和ref
可以在父组件中获取到子组件的实例 。同vue2,可利用函数可以携带值得特性,由父组件向子组件传递数据,也可以由父组件主动获取子组件的数据(getter)
父组件主动获取子组件数据
父组件,引入子组件并给其设置ref属性,并声明一个与其同名的ref变量,通过[变量名].value来获取子组件的实例对象,访问子组件向外暴露出的属性和方法
子组件,通过defineExpose()控制子组件内部哪些内容可以通过模板引用被外部访问
代码:
//parent组件
<Child ref="childRef" />
import { ref, onMounted } from 'vue'
const childRef = ref(null)
onMounted(() => {
console.log(childRef.value.child_msg)
})
//child组件
let child_msg = 'child的数据'
defineExpose({
child_msg
})
defineExpose 是 Vue 3 中 <script setup> 语法糖提供的一个编译时宏,它用于显式地暴露组件内部的属性、方法或其他响应式状态给其父组件或外部使用
5.$parent
可以在子组件内部获取到父组件的实例
父组件,通过defineExpose({})控制父组件内部哪些内容可以通过模板引用被外部访问
子组件,在调用方法时将$parent作为参数传入其中,可在方法内部访问父组件暴露的属性和方法
代码:
//parent组件
let parent_msg = 'parent的数据'
defineExpose({
parent_msg
})
//child组件
<button @click="triggerFun($parent)">触发</button>
const triggerFun = (parentItem) => {
console.log(parentItem.parent_msg)
}
vue 2中的 $children,由于违背了单向数据流,官方在vue 3中已经删除
6.$attrs
首先理解透传:官方的解释是,“透传 attribute”指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on
事件监听器。
透传进子组件的 attribute 可以在模板的表达式中直接用 $attrs(
一个组件实例上的属性)
访问到。 $attrs
对象包含了除组件所声明的 props
和 emits
之外的所有其他 attribute,例如 class
,style
,v-on
监听器等等。
和vue 2不同的是,vue 3中$listeners 属性被移除,所有的事件监听器现在都包含在 $attrs
中
父组件,引入子组件并给子组件绑定参数,监听事件。用以传递数据
子组件,通过$attrs
对象访问透传attribute
代码:
//parent组件
<Child :msg="parent_msg" :other="other" @click="testFun" />
let parent_msg = 'parent的数据'
let other = '其他'
const testFun = () => {
console.log('触发parent的testFun')
}
//child组件
//templa模板中
<p>{{ $attrs }}</p>
//script标签中
import { useAttrs } from 'vue'
defineProps(['msg'])
//直接访问attrs对象
const attrs = useAttrs()
console.log(attrs) //{other: '其他', onClick: ƒ}
更多详细内容,请查看官方文档 透传 Attributes | Vue.js
7.v - model
在组件通信中可用于实现父子组件数据同步
父组件,引入子组件,并使用v-model指令为其绑定modelValue参数用以数据的双向绑定
子组件,使用defineProps()接收message数据,使用defineEmits()定义事件update:modelValue,
详细描述:
“update:modelValue ”和 “modelValue” 是Vue为组件上的v-model组件通信提供的默认事件名和prop名约定(update: 前缀是用于指示这个事件是用来更新某个prop值的约定)。
父组件中,使用v-model通过v--model:modelValue绑定子组件定义的prop 和update:modelValue
事件。当父组件更新message时,会通过modelValue 将message传递给子组件。
子组件中,v-model会将modelValue和update:modelValue 事件绑定在一起,当子组件的input事件触发时会emit一个update:modelValue 事件,从而更新父组件的message。
代码:
//Parent组件
{{ message }}
<hr />
<Child v-model:modelValue="message" />
import Child from '@/page/Child.vue'
import { ref } from 'vue'
let message = ref('parent的数据')
//Child组件
<input type="text" :value="modelValue" @input="updateValue" />
{{ modelValue }}
const props = defineProps(['modelValue'])
const emits = defineEmits(['update:modelValue'])
function updateValue(e) {
emits('update:modelValue', e.target.value)
}
8.mitt
在vue 3中,已经没有提供配套的事件总线bus,需要使用第三方库mitt来完成vue 2中bus完成的事情。mitt是一个轻量级(仅200字节)的、功能强大的事件发射器/事件监听器库,非常适合在Vue项目或其他JS环境中使用来实现事件通信。使用Mitt可以任意组件之间进行通信
1)在项目中安装mitt
#npm
npm install mitt
#pnpm
pnpm install mitt
2)局部注册,创建mitt.js文件(实例化mitt,并对外暴露)
//引入emitt
import mitt from "mitt"
// 调用emitt得到emitter,emitter能:绑定事件,触发事件
const emitter = mitt()
export default emitter
全局注册的话,在man.js内引入mitt,并创建mitt实例,再将其挂载到Vue的全局属性上
3)在需要使用的vue文件中导入emitter
import emitter from './mitt'
4)mitt核心语法
订阅事件(监听)
使用.on
方法订阅(监听)一个或多个事件。当事件被触发时,指定的回调函数将被执行。
// 监听名为'foo'的事件
emitter.on('foo', (e) => {
console.log('foo event triggered', e);
})
// 监听所有事件(使用'*'作为通配符)
emitter.on('*', (type, e) => {
console.log(`${type} event triggered`, e);
})
发布事件(触发)
使用.emit
方法发布(触发)一个事件。可以传递任意数量的参数给监听该事件的回调函数。
// 触发名为'foo'的事件,并传递一个对象作为参数
emitter.emit('foo', { a: 'b' });
取消订阅
使用.off
方法取消订阅(移除)之前通过.on
方法添加的事件监听器。
// 假设有一个名为onFoo的回调函数
function onFoo(e) {
console.log('foo event handled', e)
}
// 订阅事件
emitter.on('foo', onFoo)
// 取消订阅
emitter.off('foo', onFoo)
// 或者,如果你没有引用回调函数,可以取消所有监听'foo'事件的监听器
emitter.off('foo')
// 取消所有事件的所有监听器
emitter.all.clear()
命名空间
Mitt 支持命名空间,这有助于避免事件名称的冲突,并允许更精细地控制事件的监听和触发。
// 使用命名空间
emitter.on('namespace/foo', (e) => {
console.log('namespaced foo event', e)
})
emitter.emit('namespace/foo', { a: 'b' })
5)使用mitt的emit方法进行传值和on方法进行接收数据
// 组件A
bus.emit('函数名', 需要传的值)
//组件B
bus.on('函数名',(接收到的值)=>{
逻辑操作
})
示例:
代码:
父组件Parent.vue
<ChildA></ChildA>
<ChildB></ChildB>
<script setup>
import ChildA from './ChildA.vue'
import ChildB from './ChildB.vue'
</script>
子组件ChildA.vue
<p>我是子组件A,我有:{{ computer }}</p>
<p>我会收到:{{ receive }}</p>
<button @click="triggerFun">传computer</button>
<script setup>
import { ref ,onUnmounted} from 'vue'
import emitter from './mitt'
//数据
let computer = ref('电脑')
let receive = ref('无')
//给emitter订阅监听send-toy事件。接收数据
emitter.on('send-toy',(item)=>{
receive.value = item.value
})
// 传递数据
function triggerFun(){
//触发emitter监听的事件send-computer
emitter.emit('send-computer',computer)
}
// 组件卸载时取消订阅。考虑内存和污染
onUnmounted(()=>{
emitter.off('send-toy')
})
</script>
子组件ChildB.vue
<p>我是子组件B,我有:{{ toy }}</p>
<p>我会收到:{{ receive }}</p>
<button @click="triggerFun">传toy</button>
<script setup>
import { ref } from 'vue'
import emitter from './mitt'
let toy = ref('玩具')
let receive = ref('无')
function triggerFun(){
//触发emitter监听的事件send-toy
emitter.emit('send-toy',toy)
}
//给emitter订阅监听send-computer事件。接收数据
emitter.on('send-computer',(item)=>{
receive.value = item.value
})
</script>
9.pinia
推荐参考:Vue状态管理工具:Pinia
可创建一个专用于组件通信的store
若有错误或描述不当的地方,烦请评论或私信指正,万分感谢 😃