Vue 通信组件传值【10】
通信组件传值
- 1.概述:
- 2.分类:
- 3.方式:
- 1.props传值:
- 概述:
- 案例:
- 测试:
- 解析:
- 2.自定义事件传值:
- 概述:
- 实现:
- 测试:
- 解析:
- 3.mitt传值:
- 概述:
- 实现:
- 1.安装mitt库
- 2.配置mitt:
- 3.发送数据的组件准备数据并发送;
- 4.接收数据的组件订阅该事件,接收数据并在模板中渲染展示
- 5.将两个组件都引用到App.vue中
- 测试:
- 解析:
- 4.v-model传值:
- 概述:
- 案例:模拟v-model实现数据双向绑定
- 案例:通过v-model实现数据传输:
- 1.发送数据:
- 2.接收数据:
- 测试:
- 解析:
- 5.$attrs祖孙传值:
- 1.实现:
- 2.测试:
- 解析:
- 6.provied和inject传值:
- 概述:
- 实现;
- 测试:
- 解析:
- 7.插槽传值:
- 概述:
- 1.插槽(Slot)的基本概念:
- 2.插槽传值的定义和作用:
- 定义:
- 作用:
- 2.使用:
- 1.默认插槽传值:
- 实现:
- 测试:
- 解析:
- 2.具名插槽传值:
- 实现:
- 测试:
- 解析:
- 3.作用域插槽传值::
- 实现:
- 测试:
- 解析:
1.概述:
组件通信即表示Vue在各组件之间相互传递数据;
2.分类:
- 子父组件传值;
- 祖孙组件传值;
- 任意组件之间传值;
3.方式:
1.props传值:
概述:
- props传值应用在父组件向子组件中传值,或子组件向父组件传值;
- 通过此方式父组件传递数据给子组件,子组件传递函数给父组件;
案例:
1.父组件准备数据发送给子组件;
<template>
//展示父组件数据
<h1>我是父组件1:</h1><h2>父组件1的车:{{car}}</h2>//展示从子组件获取到的数据<h2>子组件1的玩具:{{toy}}</h2>//将父组件数据及函数传递到子组件<S1 :car="car" :getToy="getToy"></S1>
</template><script lang="ts" setup name="F1">import {ref} from "vue";import S1 from "./S1.vue";//父组件数据和函数let car=ref('大G')let toy=ref();const getToy=(value:string)=>{console.log("父组件1收到了子组件1的数据:",value)toy.value=value;}
</script><style scoped></style>
2.子组件接收数据,并发送数据到父组件
<template>
//展示从父组件传递过来的数据;<h1>我是子组件1</h1><h2>父组件1给我的车:{{car}}</h2>//将父组件传递过来的函数绑定到单击事件上,当此事件触发时将数据传输到父组件<button @click="getToy(toy)">发送数据给父组件1</button>
</template><script lang="ts" setup name="S1">
import {defineProps, ref} from "vue";//准备子类数据let toy=ref('奥特曼')//接收父类数据及函数defineProps(['car','getToy'])</script><style scoped></style>
3.将父组件引用在App.vue上
<template>
<F1></F1>
</template><script lang="ts" setup name="App">
import F1 from "./components/F1.vue";</script><style scoped></style>
测试:
通过上面测试结果可以看出,子组件中接收到了父组件传递的数据;
此时单击子组件的功能按钮,发现子组件也成功将数据传递到父组件;
解析:
在上述案例中:
- 我们在父组件中准备了一个数据car:大G,并通过引用子标签将此数据传递到子组件中;
- 同时传递到子组件中的还有一个函数getToy,此函数用于子组件传递数据给父组件;
- 在子组件中首先通过defineprops接收父组件传递过来的数据及函数,并将接收到的数据在模板中渲染展示;
- 之后通过按钮绑定事件,并将接收到的函数作为此事件的回调函数;将子组件要传给父组件的数据作为此回调函数的参数,当事件触发时就可以将数据传递到负组件中;
2.自定义事件传值:
概述:
此方式主要用于子组件传输数据到父组件
实现:
1.子组件定义自定义事件并绑定到功能按钮上准备发送数据
<template><h1>我是子组件2:</h1><br/><button @click="emit('xxx-xx','我是子组件')">发送数据给父组件2:</button>
</template><script lang="ts" setup name="S2">import {defineEmits} from "vue";const emit=defineEmits(['xxx-xx'])
</script><style scoped></style>
2.父组件引用子组件,并监听子组件的事件;
<template><h1>我是父组件2:</h1><h2>接收到子组件的数据:{{data}}</h2><hr/><S2 @xxx-xx="getDate"></S2></template><script lang="ts" setup name="F2">
import S2 from "./S2.vue";
import {ref} from "vue";
let data=ref();
const getDate=(value)=>{console.log("接收到了子组件的数据:",value)data.value=value;
}</script><style scoped></style>
3.将父组件引用到App.vue中
<template>
<F2></F2>
</template><script lang="ts" setup name="App">
import F2 from './components/F2.vue';</script><style scoped></style>
测试:
单击按钮发送数据给父组件
解析:
- 在子组件中通过defineEmits([‘xxx-xx’])自定义了一个事件’xxx-xx’并指向函数emit;
- 子组件中模板中定义按钮并绑定单击事件,并将函数emit绑定到单击事件上,当单击事件触发时就会将emit第二个参数(发送的数据)发送到接收的组件中;
- 父组件中引用子组件,并监听子组件中的自定义事件执行,当事件执行时就会将数据从子组件传递到父组件;
3.mitt传值:
概述:
mitt传值应用于任意组件之间的传值;
实现:
1.安装mitt库
npm install mitt
2.配置mitt:
在项目src/utils/emitters:用于mitt配置
//引入 mitt
import mitt from "mitt";//创建 emitter 实例
const emitter = mitt();
//订阅(接收)事件abc和xyz
emitter.on('abc', (value) => {console.log("abc事件被触发了", value)
})emitter.on('xyz', (value) => {console.log("xyz事件被触发了", value)
})
//创建定时器:每隔1秒abc和xyz发布一次数据;
setInterval(() => {emitter.emit('abc', 666)emitter.emit('xyz', 999)}, 1000)
//创建延时器:3秒后清除所有事件
setTimeout(() => {//清除所有的事件emitter.all.clear();
}, 3000)
//暴露mitt实例
export default emitter;
3.发送数据的组件准备数据并发送;
<template><h1>我是组件3:</h1><button @click="sendToy">发送数据给组件4</button>
</template><script lang="ts" setup name="F3">
import emitter from "../util/emitter";
import {ref} from "vue";let toy=ref('奥特曼')const sendToy=()=>{console.log('发送数据给组件4')emitter.emit('sendToy',toy.value)}
</script><style scoped></style>
4.接收数据的组件订阅该事件,接收数据并在模板中渲染展示
<template>
<h1>我是组件4:</h1><h2>收到组件3的数据:{{data}}</h2>
</template><script lang="ts" setup name="F4">import emitter from "../util/emitter";import {onBeforeUnmount, onUnmounted, ref} from "vue";let data=ref();emitter.on('sendToy',(value)=>{console.log('收到了组件4的数据',value)data.value=value;})//在此组件销毁之前解绑订阅的事件,避免无休止等待onBeforeUnmount(()=>{//解绑事件emitter.off('sendToy');})
</script><style scoped></style>
5.将两个组件都引用到App.vue中
<template><F3></F3><f4></f4>
</template><script lang="ts" setup name="App">
import F3 from "./components/F3.vue";
import F4 from "./components/F4.vue";</script><style scoped></style>
测试:
通过控制台可以看出此时订阅的abc和xyz事件触发了;
此时点击按钮,发送数据的组件就可以将数据传递到接收数据的组件了;
如果时间超过三秒,再点击按钮时,接收数据的组件将无法接收到数据,因为在mitt配置中,我们定义的延时器会在三秒后清除所有事件,所以在三秒后,接收数据的组件再也收不到订阅的事件了;
解析:
- 首先安装mitt库,此库通过事件的发布及订阅实现数据的传递
- 配置mitt实例,包括订阅的事件,事件的发布,及事件的清除等等
- 在数据发送的组件中,通过mitt实例的emit发布事件并携带要发送的数据;
- 数据的接收组件订阅该事件,就可以接收到对应的数据了;
4.v-model传值:
概述:
在js中我们了解过v-model指令:用于数据的双向绑定,但实际上可通过v-model指令实现数据传输,其底层是通过一个属性modelValue和一个事件 @update来实现的;
案例:模拟v-model实现数据双向绑定
<template><h2>姓名为:{{name}}</h2>
<!-- 通过输入事件改变单向绑定为双向绑定 --><inputtype="text":value="name"@input="name= (<HTMLInputElement>$event.target).value"/></template><script lang="ts" setup name="App">import {ref} from "vue";import Test from './components/Test.vue'let name=ref('默认数据');</script><style scoped></style>
说明:
:value="name"
:这是 Vue.js 中的属性绑定语法(:
是v-bind
的缩写),它会将name
变量的值作为input
文本框的value
属性的值进行绑定。这样,当组件渲染时,文本框初始会显示name
变量的初始值,即 “默认数据”。这实现了从数据到视图的单向绑定,确保视图能根据数据的初始状态正确显示。@input="name = (<HTMLInputElement>$event.target).value"
:这是事件绑定语法,当文本框触发input
事件(即用户在文本框中输入内容时),会执行绑定的这个表达式。$event
是 Vue.js 在事件处理函数中自动提供的事件对象,$event.target
指向触发事件的 DOM 元素,在这里就是文本框本身。通过(<HTMLInputElement>$event.target)
将其转换为HTMLInputElement
类型(确保能正确获取其value
属性的值),然后将获取到的文本框的新value
值赋给name
变量。这样,当用户在文本框中输入新内容时,name
变量的值就会实时更新,从而实现了从视图到数据的反向更新,与前面的单向绑定结合起来就构成了双向数据绑定。
案例:通过v-model实现数据传输:
1.发送数据:
<!--v-model传值-->
<template><h2>姓名为:{{name}}</h2>
<!--通过v-model实现传值:下面为v-model的底层细节展示--><Test v-model="name"></Test><p>--------------------------------</p><Test :modelValue="name" @update:model-value="name=$event"></Test>
</template><script lang="ts" setup name="App">import {ref} from "vue";import Test from './components/Test.vue'let name=ref('默认数据');</script><style scoped></style>
2.接收数据:
<template><h2>测试组件:</h2><inputtype="text":value="modelValue"@input="emit('update:model-value',$event.target.value)"/>
</template><script lang="ts" setup name="Test">
import {defineEmits, defineProps} from "vue";defineProps(['modelValue'])//声明事件const emit=defineEmits(['update:model-value'])
</script><style scoped></style>
测试:
通过测试结果可以看出接收数据的组件接收到了数据;
当接收端的数据改变后,发送组建的数据同时也发生了改变;
解析:
- 案例中本质上是通过v-model指令将数据从发送组件发送到接受组件;
- v-model指令底层是通过一个属性modelValue和一个事件@update="model-value"来实现数据的双向绑定的;
- 在案例中我们模仿v-model的底层,通过属性modelValue发送数据到接收组件;
- 在接受组件中接收该数据,并模拟v-model指令的底层定义事件update=“model-value”;
- 在接收端的输入框中绑定事件,当输入框内容改变时,将输入框内容改变后的新值发送到发送组件中;
- 发送组件监听该事件,当事件触发时,就可获取到输入框数据更新后的值了;
5.$attrs祖孙传值:
1.实现:
1.祖组件App.vue准备数据发送到父组件
<template><h2>我是App组件</h2><h3>a:{{a}}--b:{{b}}</h3><h3>c:{{c}}--d:{{d}}</h3><F5 :a="a" :b="b" :c="c" :d="d" v-bind="{x:100,y:200}" :updateA="updateA"></F5></template><script lang="ts" setup name="App">import {ref} from "vue";import F5 from './components/F5.vue';let a=ref(1);let b=ref(2);let c=ref(3);let d=ref(4);const updateA=(value)=>{a.value=value;}</script><style scoped></style>
2.父组件将数据发送到子组件
<template>
<h2>我是F5组件</h2><hr/><S5 v-bind="$attrs"></S5><hr/>
</template><script lang="ts" setup name="F5">
import S5 from "./S5.vue";
import {defineProps} from "vue";//如果父组件接收了参数,则孙组件就无法接收到该数据;
//defineProps(['x','y'])
</script><style scoped></style>
3.子组件接收数据,并渲染展示
<template>
<h2>我是S5组件</h2><h2>收到App组件的数据:</h2><h3>a:{{a}}--b:{{b}}</h3><h3>c:{{c}}--d:{{d}}</h3><h3>x:{{x}}--y:{{y}}</h3><button @click="updateA(999)">点击更新数据a</button>
</template><script lang="ts" setup name="S5">import {defineProps} from "vue";defineProps(['a','b','c','d','x','y','updateA'])
</script><style scoped></style>
2.测试:
通过测试结果可以看出子组件接收到了App组件传递的数据;
当点击数据更新按钮时,可以发现在子组件和App.vue组件中的a都发生了改变;
解析:
- 在App.vue中发送了数据a,b,c,d,x,y和函数updateA到父组件;
- 父组件通过v-bind="$attrs"将数据转发到子组件中
- 子组件通过defineprops接收来自于App组件的数据和函数;
- 将接收到的数据进行渲染展示,并将接收到的函数作为事件的回调函数绑定在按钮上,当事件触发时,更新数据会发送到App组件中;
- 注意:如果在父组件中接收了App组件传给Son组件的数据,那么在Son组件中将无法在接受到该数据;
6.provied和inject传值:
概述:
此方式也用于祖孙组件之间的传值,但与$attrs不同的是,此方式不需要借助父组件进行转发,而更像是通过发布订阅模式来直接实现祖孙组件之间的数据传递;
实现;
1.App组件准备数据,并进行发布数据
<template><h3>父组件</h3><h4>资产:{{ money }}</h4><h4>汽车:{{ car }}</h4><button @click="money++">资产++</button><button @click="car.price++">汽车价格++</button><F6></F6>
</template><script setup lang="ts" name="App">
import {provide, reactive, ref} from "vue";
import F6 from './components/F6.vue'let money = ref(100);
let car = reactive({brand: '奔驰',price: 100
})const updateMoney = (value: number) => {money.value += value
}
//提供数据
provide('moneyContext', {money, updateMoney})
provide('carContext', car)</script><style scoped></style>
2.父组件引用子组件(不做其他处理)
<template><h1>我是Father组件</h1><hr/><S6></S6><hr/>
</template><script lang="ts" setup name="F6">
import S6 from "./S6.vue";</script><style scoped></style>
3.子组件接收数据
<template><h3>我是孙组件</h3><h3>资产:{{ money }}</h3><h3>汽车:{{car}}</h3><button @click="updateMoney(888)">更新资产</button>
</template><script lang="ts" setup name="S6">
import {inject} from "vue";
//接收并解析App组件传递过来的数据及函数;
//inject第二个参数表示默认值;
let {money, updateMoney} = inject('moneyContext', {money: 0, updateMoney: (x: number) => {}
})let car = inject('carContext');
</script><style scoped></style>
测试:
通过测试结果可以看出Son组件接收到了App组件传递的数据;
当App组件中的数据发送更新后,Son组件仍能接收到最新的数据;
当Son组件中修改了接收到的数据时,此修改仍能同步到App组件中;
解析:
- App组件中准备数据,并通过provied实现数据提供(发布);
- Father组件仅仅引用展示Son组件,不做其他处理;
- Son组件通过inject接收App组件发布的数据和函数,并将函数作为回调函数绑定到当前组件模板的功能按钮上,实现了Son组件与App组件数据的双向传递和同步更新;
7.插槽传值:
概述:
1.插槽(Slot)的基本概念:
- 在 Vue.js 中,插槽是一种用于组件复用和内容分发的机制。它允许在组件的模板中定义一个 “占位符”,这个占位符可以在使用该组件的时候被填充不同的内容。就像是一个有 “空洞” 的模具,在不同的使用场景下,可以往这个 “空洞” 里放入不同的材料来塑造出不同的形状。
- 插槽分为默认插槽、具名插槽和作用域插槽。
2.插槽传值的定义和作用:
定义:
插槽传值是指在使用插槽的过程中,将数据从父组件传递到子组件插槽内部使用的一种方式。
作用:
- 增强组件复用性:可以让一个组件在保持自身结构和功能的基础上,根据不同的使用场景接收并展示不同的数据。例如,一个通用的卡片组件,通过插槽传值可以在不同的页面展示不同的标题、内容和图片等信息。
- 灵活的内容分发:使父组件能够更好地控制子组件内部的部分内容,实现更细粒度的组件定制。比如,在一个布局组件中,父组件可以通过插槽传值决定子组件中的某个区域是显示文本、按钮还是其他自定义的元素。
2.使用:
1.默认插槽传值:
实现:
1.App.vue组件发送数据:
<template><Category title="热门游戏"><ul><li>植物大战僵尸</li><li>王者荣耀</li><li>原神</li></ul></Category><Category title="热门景点"><img src="./assets/vue.svg"></Category>
</template><script setup lang="ts" name="App">import Category from './components/Category.vue';
</script><style scoped></style>
2.接收数据:
<template><div style="width: 200px;background-color: skyblue"><h3>{{ title }}</h3><!-- 默认插槽 --><slot></slot></div>
</template><script setup lang="ts" name="Category">
defineProps(['title'])</script><style scoped>
h3 {background-color: orange;text-align: center;
}</style>
测试:
解析:
- App组件两次引用自定义组件Category,用于向自定义组件传输不同的数据;
- 自定义组件通过defineprops接收App组件传递过来的标题title;
- 自定义组件通过
<slot></slot>
定义了一个默认插槽,用于接收父组件在使用该子组件时填充进来的内容,比如前面父组件传递的无序列表或图片等内容;
2.具名插槽传值:
实现:
1.App组件发送数据:
<template><Category title="热门游戏"><template v-slot:s1><ul><li>植物大战僵尸</li><li>王者荣耀</li><li>原神</li></ul></template><template #s2><a href="#">更多...</a></template></Category><Category title="热门景点"><template #s2><img src="./assets/vue.svg"></template><template #s1><h4>点击查看</h4></template></Category>
</template><script setup lang="ts" name="App">import Category from "./components/Category.vue";
</script><style scoped></style>
2.接收数据:
<template><div style="width: 200px;background-color: skyblue"><h3>{{ title }}</h3><!-- 具名插槽 --><slot name="s1"></slot><p>我是一段内容</p><slot name="s2"></slot></div>
</template><script setup lang="ts" name="Category">
defineProps(['title'])</script><style scoped>
h3 {background-color: orange;text-align: center;
}</style>
测试:
解析:
App组件中:
- 这里两次使用了自定义组件
Category
,每次使用时都通过属性绑定title="热门游戏"
或title="热门景点"
给子组件传递标题属性。 - 对于每个
Category
组件的使用,父组件通过具名插槽向子组件的不同区域传递不同内容: - 当使用
Category
且设置了title="热门游戏"
时:
- 通过
<template v-slot:s1>
向名为s1
的具名插槽传递了一个包含热门游戏列表的无序列表<ul>
。 - 通过
<template #s2>
(#
是v-slot
的缩写)向名为s2
的具名插槽传递了一个带有链接的文本<a href="#">更多...</a>
。
- 当使用
Category
且设置了title="热门景点"
时:
-
通过
<template #s2>
向名为s2
的具名插槽传递了一张图片<img src="./assets/vue.svg">
。 -
通过
<template #s1>
向名为s1
的具名插槽传递了一个<h4>
标题标签,内容为 “点击查看”。自定义组件中:
- 首先定义了一个
<div>
容器,设置其宽度为200px
且背景颜色为skyblue
,用于包裹子组件的内容。 - 在
<div>
内部,通过插值表达式{{ title }}
展示了从父组件传递过来的title
属性的值,会将对应的标题(如 “热门游戏” 或 “热门景点”)显示在一个<h3>
标题标签内,并且设置了<h3>
标签的背景颜色为orange
且文本居中对齐(通过下面的样式部分设置)。 - 接着,通过
<slot name="s1"></slot>
和<slot name="s2"></slot>
分别定义了名为s1
和s2
的具名插槽,用于接收父组件通过具名插槽传递过来的不同内容,并且在两个具名插槽之间还添加了一段普通文本<p>我是一段内容</p>
3.作用域插槽传值::
实现:
1.自定义组件准备数据发送给App组件:
<template><div style="width: 200px;background-color: skyblue"><h3>{{ title }}</h3><!-- 作用域插槽 --><slot :games="games" msg="测试数据"></slot></div>
</template><script setup lang="ts" name="Category">
import {reactive} from "vue";defineProps(['title'])
let games = reactive([{id: '001', name: '植物大战僵尸'},{id: '002', name: '王者荣耀'},{id: '003', name: '原神'}
])</script><style scoped>
h3 {background-color: orange;text-align: center;
}</style>
2.App组件接收数据:
<template>
<!-- <Category v-slot="params" title="热门游戏">-->
<!-- <Category v-slot:default="params" title="热门游戏">--><Category #default="params" title="热门游戏"><ul><li v-for="g in params.games" :key="g.id">{{ g.name }}</li></ul></Category>
</template><script setup lang="ts" name="App">import Category from "@/components/Category.vue";
</script><style scoped></style>
测试:
解析:
子组件部分:
- 定义了一个
<div>
容器,设置宽度为200px
且背景颜色为skyblue
,用于包裹子组件的内容。 - 通过插值表达式
{{ title }}
在<h3>
标签内展示从父组件传递过来的title
属性的值,<h3>
标签的样式被设置为背景颜色是orange
且文本居中对齐(由子组件的样式部分设置)。 - 关键在于
<slot :games="games" msg="测试数据"></slot>
这行,这里定义了一个作用域插槽。通过:games="games"
将子组件内部的数据games
传递给插槽,同时还传递了一个固定的字符串msg="测试数据"
。当父组件使用这个作用域插槽时,可以获取到这些传递的数据并进行相应的处理。
App组件部分:
- 这里使用了子组件
Category
,并通过属性绑定title="热门游戏"
给子组件传递了标题属性。 - 对于作用域插槽的使用,有几种等价的写法,这里采用的是
#default="params"
(#
是v-slot
的缩写),它获取了子组件通过作用域插槽传递过来的数据,并将其赋值给params
变量。然后在<ul>
列表中,通过v-for="g in params.games"
循环遍历params.games
(即子组件传递过来的游戏数据数组),并将每个游戏的name
属性值通过插值表达式{{ g.name }}
展示在<li>
标签内。