主要有九种方法,以下是详细解释及使用方法:
1.props
props实现父子间的通信,是使用频率最高的。
(1)父传子:属性值是非函数。
以Father.vue和Child.vue 为例。
父组件中,引入子组件并给子组件绑定一个car参数,
子组件中,使用defineProps([''])接收,在template中,可以直接使用car
(2)子传父:属性值是函数
其实子传父前提父亲先给子传递一个函数,子接收到了再传给父。
比如我要把儿子的toy传给父亲,那么就需要在父中先定义一个getToy函数,用:sendToy=''getToy"传给子,然后子再去调用sendToy。
2.自定义事件emit
1. 概述:
自定义事件常用于:子 => 父
2. 原生Dom事件、自定义事件
(1)原生Dom事件:
- 事件名是特定的(click、dbdlick、change等等)
- 事件对象`$event`: 是包含事件相关信息的对象(`pageX`、`pageY`、`target`、`keyCode`)
(2)自定义事件:
- 事件名是任意名称
- <strong style="color:red">事件对象`$event`: 是调用`emit`时所提供的数据,可以是任意类型!!!</strong >
vue2中 @click是自定义事件,可以通过.native修饰符变为原生DOM事件。
在vue3中@click是原生DOM事件
`$event是事件对象
3. 例如:
子组件传给父亲toy,就可以这样做:父组件自定义一个send-toy方法,然后子组件使用defineEmits接收,再用emit传给父亲,之后父亲利用send-toy的回调保存传输过来的toy
3.mitt
1.概述
与消息订阅与发布(`pubsub`)功能类似,可以实现任意组件间通信。
就是只要任意两个组件能够摸到emitter,就能实现通信。
2.使用mitt
(1)安装mitt
npm i mitt
(2)新建文件:src\utils\emitter.ts
// 引入mitt
import mitt from "mitt";// 创建emitter
const emitter = mitt()/*// 绑定事件emitter.on('abc',(value)=>{console.log('abc事件被触发',value)})emitter.on('xyz',(value)=>{console.log('xyz事件被触发',value)})setInterval(() => {// 触发事件emitter.emit('abc',666)emitter.emit('xyz',777)}, 1000);setTimeout(() => {// 清理事件emitter.all.clear()}, 3000);
*/// 创建并暴露mitt
export default emitter
3.使用
emit触发,on绑定。
比如弟弟child2想要哥哥child1的玩具 ,那child2要用emitter.on绑定事件,然后child1用emitter.emit触发事件。
(注意:这里最好用onUnmounted解绑一下事件,如果有一天这个组件被卸载了,但是他化存在这个关系,就会对内存不友好)
4.v-model
1.本质
实现父↔子间通信
其实一般很少使用v-model,但是在UI组件库中会大量使用v-model.
2.使用
(1)v-model用在html标签上
v-model本质其实是value和input的结合使用
<!-- v-model用在html标签上 --><!-- <input type="text" v-model="username"> --><input type="text" :value="username" @input="username = (<HTMLInputElement>$event.target).value"><!-- <HTMLInputElement>是说明$event.target是一个html中输入类型的元素 -->
可以实现双向绑定
(2)v-model用在组件标签
只需要在组件标签中v-model="username",然后引入UI组件库。
组件标签上的`v-model`的本质:`:moldeValue` + `update:modelValue`事件。
组件标签中:
<!-- 组件标签上使用v-model指令 --><AtguiguInput v-model="userName"/><!-- 组件标签上v-model的本质 --><AtguiguInput :modelValue="userName" @update:model-value="userName = $event"/>
UI组件库中:
<template><div class="box"><!--将接收的value值赋给input元素的value属性,目的是:为了呈现数据 --><!--给input元素绑定原生input事件,触发input事件时,进而触发update:model-value事件--><input type="text" :value="modelValue" @input="emit('update:model-value',$event.target.value)"></div></template><script setup lang="ts" name="AtguiguInput">// 接收propsdefineProps(['modelValue'])// 声明事件const emit = defineEmits(['update:model-value'])</script>
(3) 修改modelValue
可以修改modelValue的话,就意味着可以在组件标签上使用多个v-model.
多个v-model
5.$attrs
1. 概述
`$attrs`用于实现当前组件的父组件,向当前组件的子组件通信(祖孙通信)。
2. 具体说明
`$attrs`是一个对象,包含所有父组件传入的标签属性。
> 注意:`$attrs`会自动排除`props`中声明的属性(可以认为声明过的 `props` 被子组件自己“消费”了)
3.使用
先来了解一下attrs
首先父向子传递多个响应式数据,但是子只接受一个a,那么剩下的都去哪里了呢。其实在attrs中存着,我们可以用$attrs看到
(1)祖传孙:
假如上述父子传递时,子不接收呢?而是用v-bind传给孙组件呢?
那么在孙组件中就可以用defineProps接收,并呈现出来。
这样就实现了祖向孙通信
(2)孙传祖
在祖里面定义有一个方法
并把方法也传给子
<Child :a="a" :b="b" :c="c" :d="d" v-bind="{x:100,y:200}" :updateA="updateA"/>
而子中依旧是这样:
<GrandChild v-bind="$attrs"/>
那么孙中只要接收到了方法,就可以绑定一个点击事件,把自己的定义的数字6传给祖的value,实现孙传祖。
6.$refs、$parent
1. 概述:
$refs`用于 :父→子。
$parent`用于:子→父。
2.原理如下
3.使用
(1)ref
先来看一下用ref是怎么修改的。首先用ref给Child1打个标识c1,然后配置一个修改方法changeToy
之后在child1中配置difineExpose就可以了,这样父就能点击修改子的玩具。实现父到子通信。
// 把数据交给外部defineExpose({toy,book})
同理如果父亲想要修改child2的数据也同理。
(2)$refs
在父组件中用如下写法可以获取所有子组件的实例对象
<button @click="getAllChild($refs)">获取所有子组件的实例对象</button>
如图所示:可以获取
那么就可以利用refs修改所有子组件共同想修改的内容,例如修改所有子组件的玩具数量。
记得在所有子组件里面配置defineExpose
// 把数据交给外部defineExpose({toy,book})
(3)$parent
通过$parent可以看到父组件的实例对象 。这样可以通过在子组件中使用$parent并配置方法,从而修改父组件数据。实现子向父通信。
千万不要忘了在父组件中配置defineExpose
// 向外部提供数据defineExpose({house})
7.provide、inject
1.概述
实现祖孙组件直接通信
2. 具体使用
* 在祖先组件中通过`provide`配置向后代组件提供数据
* 在后代组件中通过`inject`配置来声明接收数据
3.例如
(1)祖传孙
例如把爷爷有的钱和车子都传给孙,应该怎么做呢?
在父组件中先从vue中import引入provide ,而后使用provide向后代提供数据
provide('名字',值) 需要注意的是这个值后面不要.value,这样会丢失响应式
在后代组件中,先import引入inject,而后使用inject接收数据。
需要注意的是,inject('名字','默认值')这2个参数 一个是名字一个是默认值,如果不写默认值的话,上面的car.brand和car.price就会报错。
(2)孙传祖
比如孙子华爷爷的钱。在孙组件里面点击一下按钮就能修改爷爷的钱。
首先在祖组件里面定义一个方法,在provide里面传的是money的值和这个方法给孙组件
那么在孙组件里面可以把传过来的值和方法 利用解构赋值,然后再添加一个按钮绑定这个方法,并传数据。就实现了孙传祖。
8.pinia
详见上一节:Vue3(四):Pinia-CSDN博客
9.slot插槽
假如要实现这样一个效果:
1.默认插槽
slot里面是默认内容,如果没有要插进来的内容,就会显示默认内容
//父组件中:<Category title="今日热门游戏"><ul><li v-for="g in games" :key="g.id">{{ g.name }}</li></ul></Category>
//子组件中:<template><div class="item"><h3>{{ title }}</h3><!-- 默认插槽 --><slot></slot></div></template>
2.具名插槽
在子组件标签里面把需要传递的内容使用template标签包括,用v-slot命名,也可以用#命名。 子组件slot中用相应的name="xxx"
父组件中:<Category title="今日热门游戏"><template v-slot:s1><ul><li v-for="g in games" :key="g.id">{{ g.name }}</li></ul></template><template #s2><a href="">更多</a></template></Category>
子组件中:<template><div class="item"><h3>{{ title }}</h3><slot name="s1"></slot><slot name="s2"></slot></div></template>
3.作用域插槽
像下面这样,父组件拿games会报错,因为这时候父组件是拿不到子组件的games的,涉及到一个作用域的内容。
对于作用域插槽的解释:可以这样理解
语法及使用:
第一种是使用v-slot="params",意思是吧template里面的内容放在默认插槽里面
第二种是也可以使用命名的方式,因为slot有默认的命名,default,所以可以用#default
10.总结