AiShang - 爱尚IT分享博客AiShang - 爱尚IT分享博客AiShang - 爱尚IT分享博客

Vue3 组件通信

一、props

概述:props是使用频率最高的一种通信方式,常用与 :父 ↔ 子

父传子:属性值是非函数。若 子传父:属性值是函数。1.1、父组件
登录后复制
<template> <div class="app"> <h2>父组件</h2> <p v-if="drug"> 子组件中的药剂:{{ drug }} </p> <p> 余额:{{ amount }} </p> <hr /> <Subcomponent :money="amount" :sendDrug="getDrug" /> </div> </template> <script setup lang="ts" name="App"> import Subcomponent from ./components/Subcomponent.vue; import { ref } from vue; // 数据 let amount = ref(666) let drug = ref() // 方法 function getDrug(d: string) { drug.value = d } </script> <style lang="scss" scoped> .app { margin: 150px; padding: 30px; background-color: bisque; } </style>1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.
1.2、子组件
登录后复制
<template> <div class="subcomponent"> <h2>子组件</h2> <p> 父组件中的余额:{{ money }} </p> <p> 药剂:{{ drug }} </p> <button @click="sendDrug(drug)">传你一副药剂</button> </div> </template> <script setup lang="ts" name="Subcomponent"> import { ref } from vue; // 接收父组件传来的参数 defineProps([money, sendDrug]) // 或者下面这样接收。下面这种接收方式就可以在当前setup中直接调用sendDrug(xxx) // const props = defineProps<{money?: number, sendDrug: Function}>() // let { money, sendDrug } = props // sendDrug(xxx) let drug = ref(春嘿嘿) </script> <style lang="scss" scoped> .subcomponent { margin: 50px; padding: 50px; background-color: coral; } button { margin-right: 20px; background-color: beige; } </style>1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.
1.3、最终效果(父组件向子组件传余额,以及修改药剂的函数)

二、Pinia

2.1、在src/store目录下新建一个xxx.ts文件。(这就相当于共享数据)
登录后复制
import axios from "axios"; import { defineStore } from "pinia"; import { reactive } from "vue"; const useLoveTalkStore = defineStore(love-talk-id, () => { // 相当于[state-状态] 先从浏览器的 localStorage 中取值 const loveTalks: string[] = reactive(JSON.parse(localStorage.getItem(loveTalks) as string) || []) // 相当于[actions-动作] function addLoveTalk() { // loveTalks 数组头部插入 loveTalks.unshift(content) // 存储到浏览器的 localStorage 中,实现页面刷新数据不丢失 localStorage.setItem(loveTalks, JSON.stringify(loveTalks)) } // 向外暴露 return { loveTalks } }) // 暴露useLoveTalkStore export default useLoveTalkStore1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.
2.2、在其他组件中引入xxx.ts文件,然后就都能获取到state数据,同时也可根据xxx.ts文件中提供的方法对state进行修改,这样就相当于各组件实现相互通信。
登录后复制
// 引入loveTalk.ts import useLoveTalkStore from ./store/loveTalk; import { storeToRefs } from pinia; import { toRefs } from vue; let loveTalkStore = useLoveTalkStore() // 可以直接从 useLoveTalkStore() 结果中解构方法 const { addLoveTalk } = loveTalkStore const { loveTalks } = storeToRefs(loveTalkStore)1.2.3.4.5.6.7.8.9.

三、mitt(其实就是先在一个组件中绑定事件,然后其他组件只需要触发这个事件就能完成组件之间的通信。只不过你想做什么样的操作,就需要先绑定什么样的事件)

3.1、安装mitt
登录后复制
npm i mitt1.
3.2、新建src/utils/emitter.ts文件,创建mitt实例并暴露
登录后复制
// 引入mitt import mitt from "mitt"; // 创建mitt const emitter = mitt() // 暴露mitt export default emitter1.2.3.4.5.6.7.8.
3.3、在接收数据方绑定事件。(这里选择子组件Subcomponent.vue作为接收数据方)
登录后复制
<template> <div class="subcomponent"> <h2>子组件</h2> <p> 电脑:{{ computer }} </p> </div> </template> <script setup lang="ts" name="Subcomponent"> import emitter from @/utils/emitter; import { onUnmounted, ref } from vue; // 数据 let computer = ref<string>(惠普-暗影精灵) // 组件创建时,绑定事件[modify-computer] emitter.on(modify-computer, (value: any) => { computer.value = value }) // 组件卸载时,解绑事件!!!!!! onUnmounted(() => { emitter.off(modify-computer) }) </script> <style lang="scss" scoped> .subcomponent { margin: 50px; padding: 50px; background-color: coral; } button { margin-right: 20px; background-color: beige; } </style>1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.
3.4、在提供数据方触发事件。(这里选择父组件App.vue作为提供数据方)
登录后复制
<template> <div class="app"> <h2>父组件</h2> <button @click="changeSubComputer">改变子组件中的电脑</button> <hr /> <Subcomponent /> </div> </template> <script setup lang="ts" name="App"> import Subcomponent from ./components/Subcomponent.vue; import emitter from ./utils/emitter; // 方法。触发子组件中的事件 function changeSubComputer() { emitter.emit(modify-computer, 联想) } </script> <style lang="scss" scoped> .app { margin: 150px; padding: 30px; background-color: bisque; } </style>1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.
3.5、效果图

四、slot【作用域插槽】

数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。

4.1、定义一个共用组件Subcomponent.vue。(数据和插槽都在当前组件中)
登录后复制
<template> <div class="subcomponent"> <!-- <h2>子组件</h2> --> <!-- 给插槽绑定数据,谁使用了插槽就能拿到这些数据 --> <slot name="s1" :persons="persons" :ss="123">未知异常</slot> </div> </template> <script setup lang="ts" name="Subcomponent"> import { reactive } from vue; // 数据和插槽在同一个组件中。(可重复使用当前组件来实现同一数据不同样式) let persons = reactive([ { id: 001, name: 张三 }, { id: 002, name: 李四 }, { id: 003, name: 王五 }, { id: 004, name: 赵六 } ]) </script> <style lang="scss" scoped> .subcomponent { background-color: skyblue; border-radius: 10px; box-shadow: 0 0 10px; padding: 10px; width: 200px; height: 300px; } button { margin-right: 20px; background-color: beige; } </style>1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.
4.2、定义使用公用组件的组件App.vue。(主要实现页面结构样式)

使用插槽时,可以在template中使用#s1="params"来获取插槽中绑定的数据,并通过params.persons、params.ss的方式取出绑定的多个值。

登录后复制
<template> <div class="app"> <!-- <h2>父组件</h2> --> <!-- <hr /> --> <!-- #s1 相当于 v-slot:s1 --> <div class="content"> <Subcomponent> <!-- #s1="params":取出插槽中绑定的所有数据。也可以不叫params,取名随意。 --> <template #s1="params"> <h2>有序列表</h2> <ul> <li v-for="(p, index) in params.persons" :key="p.id"> {{ p.name }} </li> </ul> <p> <span>展示测试的值:{{ params.ss }}</span> </p> </template> </Subcomponent> <Subcomponent> <template v-slot:s1="params"> <h2>有序列表</h2> <ol> <li v-for="(p, index) in params.persons" :key="p.id"> {{ p.name }} </li> </ol> </template> </Subcomponent> <Subcomponent> <template #s1="params"> <h2>人物列表</h2> <p v-for="(p, index) in params.persons" :key="p.id"> {{ p.name }} </p> <!-- <h2>短视频</h2> --> <!-- <video :src="videoUrl" controls /> --> </template> </Subcomponent> </div> </div> </template> <script setup lang="ts" name="App"> import Subcomponent from "./components/Subcomponent.vue"; import { ref, reactive } from "vue"; // 当前页面不需要定义数据,数据都是从插槽中获取。 // let persons = reactive([ // { id: 001, name: 张三 }, // { id: 002, name: 李四 }, // { id: 003, name: 王五 }, // { id: 004, name: 赵六 } // ]) // let imgUrl = ref(https://img1.baidu.com/it/u=2723523495,2549185469&fm=253&fmt=auto&app=120&f=JPEG?w=1280&h=800"}) // let videoUrl = ref(https://media.w3.org/2010/05/sintel/trailer.mp4) </script> <style lang="scss" scoped> .app { background-color: pink; border-radius: 10px; width: 800px; margin: auto; margin-top: 100px; padding: 20px; } .content { display: flex; justify-content: space-evenly; } img,video { width: 100%; } h2 { background-color: orange; text-align: center; font-size: 20px; font-weight: 800; } </style>1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.77.78.79.80.81.82.83.84.85.86.87.88.89.90.
4.3、效果图。(相同的数据但以不同的样式展示。)

五、自定义事件

5.1、在父组件App.vue中对引入的子组件Subcomponent.vue添加自定义事件<Subcomponent @modify-car="car = $event" />。
登录后复制
<template> <div class="app"> <h2>父组件</h2> <p> 车:{{ car }} </p> <hr /> <!-- 添加自定义事件 --> <Subcomponent @modify-car="car = $event" /> </div> </template> <script setup lang="ts" name="App"> import { ref } from vue; import Subcomponent from ./components/Subcomponent.vue; let car = ref(法拉利) </script> <style lang="scss" scoped> .app { margin: 150px; padding: 30px; background-color: bisque; } </style>1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.
5.2、在子组件Subcomponent.vue中声明可以触发的自定义事件,以及触发事件
登录后复制
<template> <div class="subcomponent"> <h2>子组件</h2> <button @click="modifyCar">修改父组件中的车</button> </div> </template> <script setup lang="ts" name="Subcomponent"> // 声明组件可以触发的自定义事件 const emit = defineEmits([modify-car]) // 方法 function modifyCar() { // 触发自定义事件 emit(modify-car, 五菱宏光) } </script> <style lang="scss" scoped> .subcomponent { margin: 50px; padding: 50px; background-color: coral; } button { margin-right: 20px; background-color: beige; } </style>1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.
5.3、效果图。(点击子组件中的按钮可对父组件中的车数据进行修改)

六、$attrs

$attrs用于实现当前组件的父组件,向当前组件的子组件通信(祖→孙)。

$attrs是一个对象,包含所有父组件传入的标签属性。

注意:$attrs会自动排除props中声明的属性(可以认为声明过的 props 被子组件自己“消费”了)

6.1、父组件Parentcomponent.vue。(在引入的子组件标签中传递属性值)
登录后复制
<template> <div class="parentcomponent"> <h2>父组件: [a = {{ a }}] [b = {{ b }}] [c = {{ c }}] </h2> <hr /> <!-- 传入a, b, c, modifyName 等标签属性 --> <Subcomponent :a="a" :b="b" :c="c" :modifyName="modifyName" /> </div> </template> <script setup lang="ts" name="Parentcomponent"> import { reactive, ref } from vue; import Subcomponent from ./Subcomponent.vue; // 数据 let a = ref(1) let b = ref(qq) let c = reactive({ id: 001, name: 张三 }) // 方法 function modifyName(value: string): void { Object.assign(c, { name: value }) } </script> <style lang="scss" scoped> .parentcomponent { margin: auto; margin-top: 50px; padding: 30px; background-color: antiquewhite; width: 900px; } </style>1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.
6.2、子组件(在引入的孙组件标签中添加v-bind="$attrs"即可传递属性)
登录后复制
<template> <div class="subcomponent"> <h2>子组件</h2> <hr /> <!-- 需要使用 v-bind="$attrs" 。 否则孙组件中不会接收到父组件中传来的标签属性 --> <Grandsoncomponent v-bind="$attrs" /> </div> </template> <script setup lang="ts" name="Subcomponent"> import Grandsoncomponent from ./Grandsoncomponent.vue; // $attrs会自动排除以下props中声明的属性 // 如下定义,则孙组件无法获取父组件传来的属性a的值 defineProps([a]) </script> <style lang="scss" scoped> .subcomponent { margin: auto; margin-top: 30px; padding: 30px; background-color: bisque; } </style>1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.
6.3、孙组件(通过props声明并接收父类组件传来的属性)
登录后复制
<template> <div class="grandsoncomponent"> <h2>孙组件</h2> <p> 接收到父组件中的值: [a = {{ a }}] [b = {{ b }}] [c = {{ c }}] </p> <button @click="changeNameForParentcomponent">更新父组件中的名字</button> </div> </template> <script setup lang="ts" name="Grandsoncomponent"> import { toRefs } from vue; // 声明并接收父组件中传来props属性 const props = defineProps<{ a?: number, b?: string, c?: object, modifyName?: Function }>() console.log(props === , props); const { a, b, c } = toRefs(props) function changeNameForParentcomponent() { // ?. modifyName 是函数就调用它;否则不执行任何操作 props.modifyName?.(李四~~~) } </script> <style lang="scss" scoped> .grandsoncomponent { margin: auto; margin-top: 30px; padding: 30px; background-color: beige; } </style>1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.
6.4、效果图。(通过孙组件中的按钮可修改父组件中的数据,实现祖孙通信)

七、$refs、$parent

$refs:值为对象,包含所有被ref属性标识的DOM元素或组件实例。常用于 :父→子

$parent:值为对象,当前组件的父组件实例对象。常用于:子→父

7.1、父组件Father.vue(通过$refs拿到当前组件的所有子组件对象,并且可对子组件暴露的数据进行修改)
登录后复制
<template> <div class="father"> <h3>父组件</h3> <h4>房产:{{ house }}</h4> <button @click="changeToy">修改Child1的玩具</button> <button @click="changeComputer">修改Child2的电脑</button> <!-- $refs 能拿到当前组件的所有子组件。若要拿到子组件中的数据或方法则需要子组件使用defineExpose暴露 --> <button @click="getAllChild($refs)">让所有孩子的书变多</button> <!-- ref 需要绑定一个变量,例如 c1 是需要自己定义的 --> <Child1 ref="c1" /> <Child2 ref="c2" /> </div> </template> <script setup lang="ts" name="Father"> import Child1 from ./Child1.vue import Child2 from ./Child2.vue import { ref } from "vue"; // 数据 let house = ref(4) let c1 = ref() let c2 = ref() // 方法 function changeToy() { c1.value.toy = 小猪佩奇 } function changeComputer() { c2.value.computer = 华为 } // $refs 能拿到当前组件的所有子组件。若要拿到子组件中的数据或方法则需要子组件使用defineExpose暴露 function getAllChild(refs: { [key: string]: any }) { console.log($refs === , refs) for (let key in refs) { refs[key].book += 3 } } // 向外部提供数据 defineExpose({ house }) </script> <style lang="scss" scoped> .father { background-color: pink; padding: 20px; border-radius: 10px; } .father button { margin-bottom: 10px; margin-left: 10px; } button { background-color: bisque; } </style>1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.
7.2、子组件①Child1.vue(可通过$parent拿到当前组件的父组件对象,并且可对父组件暴露的数据进行修改)
登录后复制
<template> <div class="child1"> <h3>子组件1</h3> <h4>玩具:{{ toy }}</h4> <h4>书籍:{{ book }}</h4> <!-- $parent 可以拿到当前组件的父组件。若要拿到父组件中的数据或方法则需要父组件使用defineExpose暴露 --> <button @click="minusHouse($parent)">干掉父亲的一套房产</button> </div> </template> <script setup lang="ts" name="Child1"> import { ref } from "vue"; // 数据 let toy = ref(奥特曼) let book = ref(3) // 方法 function minusHouse(parent: any) { console.log($parent === , parent) parent.house -= 1 } // 把数据交给外部 defineExpose({ toy, book }) </script> <style lang="scss" scoped> .child1 { margin-top: 20px; background-color: skyblue; padding: 20px; border-radius: 10px; box-shadow: 0 0 10px black; } </style>1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.
7.3、子组件②Child2.vue
登录后复制
<template> <div class="child2"> <h3>子组件2</h3> <h4>电脑:{{ computer }}</h4> <h4>书籍:{{ book }}</h4> </div> </template> <script setup lang="ts" name="Child2"> import { ref } from "vue"; // 数据 let computer = ref(联想) let book = ref(6) // 把数据交给外部 defineExpose({ computer, book }) </script> <style lang="scss" scoped> .child2 { margin-top: 20px; background-color: orange; padding: 20px; border-radius: 10px; box-shadow: 0 0 10px black; } </style>1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.
7.4、效果图。(通过父组件和子组件中各自的按钮实现父子组件互相通信)

八、provide、inject

8.1、父组件Parentcomponent.vue。(通过provide向后代组件提供数据
登录后复制
<template> <div class="parentcomponent"> <h2>父组件</h2> <h4>资产:{{ money }}</h4> <h4>汽车:{{ car }}</h4> <button @click="money += 1">资产+1</button> <button @click="car.price += 1">汽车价格+1</button> <hr /> <Subcomponent /> </div> </template> <script setup lang="ts" name="Parentcomponent"> import Subcomponent from "./Subcomponent.vue"; import { ref, reactive, provide } from "vue"; // 数据 let money = ref(100) let car = reactive({ brand: 奔驰, price: 100 }) // 用于更新money的方法 function updateMoney(value: number) { money.value += value } // 提供数据 provide(moneyContext, { money, updateMoney }) provide(car, car) </script> <style lang="scss" scoped> .parentcomponent { margin: auto; margin-top: 50px; padding: 30px; background-color: antiquewhite; width: 900px; } button { margin-right: 10px; background-color: bisque; } </style> 1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.
8.2、子组件Subcomponent.vue。(不需要编写任何过渡代码,不受影响)
登录后复制
<template> <div class="subcomponent"> <h2>子组件</h2> <hr /> <Grandsoncomponent /> </div> </template> <script setup lang="ts" name="Subcomponent"> import Grandsoncomponent from ./Grandsoncomponent.vue; </script> <style lang="scss" scoped> .subcomponent { margin: auto; margin-top: 30px; padding: 30px; background-color: bisque; } </style> 1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.
8.3、孙组件Grandsoncomponent.vue。(通过inject来声明接收数据,并且若接收的数据不存在,可以给定其默认值
登录后复制
<template> <div class="grandsoncomponent"> <h3>孙组件</h3> <h4>资产:{{ money }}</h4> <h4>汽车:{{ car }}</h4> <!-- 父组件中的方法也可通过provide提供 --> <button @click="updateMoney(6)">点我资产增加</button> </div> </template> <script setup lang="ts" name="Grandsoncomponent"> import { inject } from vue; // 注入数据 let { money, updateMoney } = inject(moneyContext, { money: 0, updateMoney: (x: number) => {} }) let car = inject(car) </script> <style lang="scss" scoped> .grandsoncomponent { margin: auto; margin-top: 30px; padding: 30px; background-color: beige; } </style> 1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.
8.4、效果图。(无需通过子组件进行数据过渡,直接实现祖孙通信)

九、v-model

9.1、父组件App.vue。(v-model的本质::moldeValueupdate:modelValue事件
登录后复制
<template> <div class="app"> <!-- 下面两个标签是等价的 --> <MyInput v-model:qqq="username" /> <!-- v-model的本质是下面这行代码 --> <!-- <MyInput :qqq="username" @update:qqq="username = $event" /> --> </div> </template> <script setup lang="ts" name="App"> import MyInput from ./components/MyInput.vue; import { ref } from vue; // 数据 let username = ref(zhangsan) </script> <style lang="scss" scoped> .app { width: 700px; margin: auto; } </style> 1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.
9.2、自定义输入框组件MyInput.vue。(声明 组件接收的props 和 可以触发的自定义事件)
登录后复制
<template> <div class="myInput"> 输入框: <!-- 将接收的qqq值赋给input元素的value属性,目的是:为了呈现数据 --> <!-- 给input元素绑定原生input事件,触发input事件时,进而触发update:qqq事件 --> <!-- v-model的本质是下面这行代码 --> <input type="text" :value="qqq" @input="allEmits(update:qqq, (<HTMLInputElement>$event.target).value)" /> </div> </template> <script setup lang="ts" name="MyInput"> // 声明组件接收的 props defineProps([qqq]) // 声明组件可以触发的自定义事件 const allEmits = defineEmits([update:qqq]) </script> <style lang="scss" scoped> input { border: 2px solid rgb(0, 0, 0); background-image: linear-gradient(45deg, rgb(248, 194, 194), rgba(189, 242, 248, 0.552), rgb(249, 208, 218)); height: 30px; font-size: 20px; color: rgb(255, 5, 5); } </style> 1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.
9.3、效果图。(在引入的自定义输入框组件中修改值,当前组件的值也同步变化)

未经允许不得转载:AiShang - 爱尚IT分享博客 » Vue3 组件通信