一文详解Vue3中的14种组件通信方式
作者:程序员小寒
对于日常使用vue3
开发项目的前端小伙伴来说,组件通信方式可以说是必会的基本功,今天带大家一起盘下vue3的通信方式。
我们按照组件的关系来划分。总共包含14中组件通信方式。
一:父子通信
1.1、父传子:props
最最常用的通信方式是props
了,父组件通过props
方式将属性传递给子组件,子组件接受props
并用于数据操作和页面渲染。
注意:子组件不要直接修改父组件传递过来的props,保持自上而下单项数据流,这样会让数据的流向十分清晰,方便后续维护!
// Parent.vue <template> <Child :msg="msg"/> </template> <script setup> import { ref } from 'vue'; import Child from './components/Child.vue'; const msg = ref('hello world'); </script>
// Child.vue <template> <div> propsData:{{ msg }} </div> </template> <script setup> defineProps({ msg: String }) </script>
1.2、子传父:defineEmits
通过defineEmits
可以让子组件的值传递到父组件中。
其用法如下:
先在子组件中用defineEmits([...emitName])
定义一个或多个emit
,它的返回值是一个emits
函数,然后可以通过调用emits
函数向父组件发射时间,并携带参数。
// Child.vue <template> <div @click="onClick"> child </div> </template> <script setup> const emits = defineEmits(['update']); const onClick = () => { emits('update', 'child update'); } </script>
在父组件中通过@符 + 事件名
监听子组件发射 出来的事件,并接收其传过来的值。
// Parent.vue <template> <div> Parent <Child @update="update"/> </div> </template> <script setup> import Child from './Child.vue'; const update = val => { console.log(val); // 当子组件点击事件触发后,这里会打印 child update } </script>
在vue2
的组件中还可以通过this.$on
、this.$emit
来监听、发射事件,以达到传值的目的,但在vue3中已废弃这种写法。
1.3、$attrs
如果需要在子组件中接收的props
很多,如果在props
声明比较繁琐,所以vue给我们提供了一个优雅的解决方案,即$attrs
,$attrs
指的是父组件传递给子组件的所有属性中,剔除在props
中定义的那部分之后,剩下的就会放在$attrs
中。
举个例子:
// Parent.vue <template> <div> Parent <Child :msg1="1" :msg2="2" /> </div> </template>
这里父组件给子组件传递了两个属性msg1
、msg2
。
// Child.vue <template> <div> child: {{ $attrs }} </div> </template> <script setup> defineProps({ msg1: String }) </script>
这里子组件使用了defineProps
定义了msg1
,则页面中$attrs
的值为{ msg2: 2 }
。
还可以使用v-bind
将$attrs
的所有数据,以属性的方式全部传递到子组件中,我们平常在封装组件的时候,这个东西就能帮助我们实现组件的属性透传,十分好用。
<template> <Child v-bind="$attrs"/> </template>
注意:在vue3中$listeners
已废弃,无法使用。
1.4、$ref + defineExpose
通过$ref
可以拿到组件的实例,defineExpose
可以显式指定在 <script setup>
组件中要暴露出去的属性,它两一起配合使用,就能实现父子组件的通信。
其用法如下:
在子组件中通过defineExpose
暴露一个update
方法。
// Child.vue <script setup> defineExpose({ update(val) { console.log('父组件传递过来的值', val); } }) </script>
在父组件中通过ref
拿到组件实例并调用子组件暴露的update
方法。
// Parent.vue <template> <div> Parent <Child ref="childRef"/> </div> </template> <script setup> import Child from './Child.vue'; import { ref, onMounted } from 'vue'; const childRef = ref(null); onMounted(() => { childRef.value.update('hello') }) const update = () => {} </script>
1.5、$parent
$parent
代表当前组件的父组件实例,如果当前组件是顶层组件,则$parent
的值为null
。
我们可以通过$parent
拿到父组件的实例,自然就可以进行父子组件的交互了。一般也是和defineExpose
配合使用,和$ref + defineExpose
用法类似,这里就不多说了。
注意:$children
在vue3中已经废弃,无法使用。
1.6、作用域插槽
通过作用域插槽可以实现子组件向父组件传递数据。
子组件代码:
<template> <div> <slot :data="{ a:1, b: 2 }"/> </div> </template>
子组件可以在slot
标签上传递数据给父组件。
父组件代码:
<template> <Child> <template v-slot="slotProps"> {{ slotProps.data }} </template> </Child> </template> <script setup> import Child from './Child.vue'; </script>
父组件用v-slot
来接收数据,并渲染到页面上。
1.7、v-model
v-model
可以在组件上使用以实现双向绑定,vue
内部会帮你传递值和绑定事件,也是达到了父子组件通讯的效果。
从vue3.4
开始,还可以使用defineModel
便利宏,其用法如下:
子组件代码:
// Child.vue <template> <div>model的值: {{ model }}</div> <button @click="handleClick">+1</button> </template> <script setup> const model = defineModel() function handleClick() { model.value++ } </script>
父组件代码:
// Parent.vue <template> <Child1 v-model="modelValue"></Child1> Parent:{{ modelValue }} </template> <script setup> import Child from './Child.vue'; const modelValue = ref(0) </script>
defineModel
的返回值就是一个ref
,你可以随意访问和修改它,并且它会和父组件的v-model
绑定的值保持同步,也就是实现了双向绑定。
二、兄弟组件
两个兄弟关系组件进行通信,我们一般会借助第三方媒介。
2.1、mitt
mitt
相当于我们vue2
的事件总线$bus
,只是vue3
将其废弃,所以我们借助mitt
实现类似$bus
的效果。
用法如下:
安装mitt
npm install mitt
初始化mitt
// emitter.js import mitt from'mitt'; export default mitt();
兄弟组件1:
<script setup> import emitter from '@/utils/emitter' emitter.on('update', (val) => { console.log('update事件触发', val) }) </script>
兄弟组件2:
<script setup> import emitter from '@/utils/emitter' setTimeout(() => { emitter.emit('update', 'hello') }, 1000) </script>
2.2、$parent
我们可以把状态(即数据)定义在父组件中,两个兄弟组件可以借助其共同的父组件共享同一份数据,间接实现通信。
2.3、vuex/pinia
vuex
是vue官方
提供的状态管理工具,用它可以实现全局的状态共享,自然也可以实现兄弟组件的通信了。当然也可以使用pinia
替代vuex
。
2.4、app.config.globalProperties
app.config.globalProperties
是一个全局的对象,在应用内所有组件实例都能访问到,当组件属性名和它发生同名冲突时,采取就近原则,以组件的为准,这个就相当于vue2
的Vue.prototype
。
三、跨层级通信
3.1、mitt
mitt
可以实现全局的通信,这个在上面介绍兄弟组件通信
的时候已经说过,这里不再多说了。
3.2、vuex/pinia
vuex
和pinia
都是全局的状态管理工具,跨层级通信也不再话下。
3.3、provide/inject
provide/inject
是vue3
提供的可以跨层级通信的方式。
其用法如下:
在父组件/根组件
中定义provide
,提供数据。
// App.vue <script setup> import { ref, provide } from 'vue'; const name = ref('sam'); provide('name', name) </script>
在子组件/孙子组件
中使用inject
,注入数据。
// 后代组件 <script setup> import { inject } from 'vue'; const name = inject('name'); console.log('name', name.value); // 输出name </script>
四、其它方式
4.1、浏览器本地存储storage
html5
提供了一套storage API
,包括了localStorage
和sessionStorage
,它实现持久化存储、缓存等功能,自然也可以用来组件间通信了。
// 组件A <script setup> sessionStorage.setItem('name', 'jack'); </script>
// 组件B <script setup> setTimeout(() => { console.log(sessionStorage.getItem('name')); //打印 jack }, 1000) </script>
这里我在组件A使用sessionStarge
设置了一个name
值,在组件B里面就能拿到了,只要保证获取值在设置值之后执行就行了。
4.2、全局window
对象(不推荐使用)
window
作为一个全局对象,当然也可以使用它来通信了,不过它既然谁都可以访问到,就存在如下问题:
- 命令冲突问题;
- 难以追踪数据修改,可维护性差;
- 挂在window上的数据难以销毁,从而造成内存泄漏。
所以不推荐使用window
对象进行通信。
4.3、 ES6模块化import/export
我们可以使用ES5的模块化规范import/export
实现通信。
// a.js export let a = undefined; setTimeout(() => { a = 1; }, 1000)
// b.js import { a } from './a.js' setTimeout(() => { console.log(a); // 打印1 }, 2000)
由于ES module
采用的是符号绑定
,所以就算export
的值是一个基本数据类型的值
,后续修改了也能访问到。
以上就是一文详解Vue3中的14种组件通信方式的详细内容,更多关于Vue3组件通信方式的资料请关注脚本之家其它相关文章!