Vue3 学习-组件通讯(二)

news2024/11/24 19:27:18

文章目录

  • 前言
  • 一、props通信
  • 二、自定义事件(emit)
  • 三、全局事件总线(EventBus)
  • 四、v-model
  • 五、userAttrs
  • 六、ref和$parent
  • 七、Provide与Inject
  • 八、pinia
  • 九、slot插槽
  • 总结


前言

本文主要记录Vue3的九种组件通信方式


一、props通信

子组件需要用defineProps方法接收父组件传递的数据,该方法不用引入,为Vue3自带,直接使用

  • 父组件
<script setup lang="ts">
import Son from "./Son.vue";
import {ref} from "vue";
let money = ref<Number>(1000)
</script>

<template>
  <Son info="我是父组件" :money="money"/>
</template>
  • 子组件
<script setup lang="ts">
//需要用defineProps方法接收父组件传递的数据
//该方法不用引入,为Vue3自带,直接使用
//通过props获取的数据是代理对象
//props的数据是只读的,不可修改
let props = defineProps(['info','money'])
console.log(props)
</script>

<template>
  <div>
    <p>{{info}}</p>
    <p>{{money}}</p>
  </div>
</template>

通过props获取的数据是代理对象
props的数据是只读的,不可修改

在这里插入图片描述

二、自定义事件(emit)

子组件需要用defineEmits方法接收父组件传递的自定义事件,引入后可向父组件传值,该方法不用引入,为Vue3自带,直接使用

前置

  • 在vue2中在子组件添加原生事件时默认为自定义事件,需要通过.native修饰符变为原生DOM事件
  • 在Vue3中组件添加原生事件时为原生事件,当子组件绑定时变为自定义事件
  • 原生事件会带一个event回调函数用来记录当前事件的一些属性。
const handler2 = (event) => {
  //event为事件对象
  console.log(event)
}

 <h1>事件</h1>
    <pre @click="handler2">
      点击原生事件event
    </pre>

事件event


父组件在子组件标签上绑定自定义事件,当绑定的为原生事件时,如果子组件没有使用,则默认为原生事件,二子组件使用则为自定义事件。

<hr>
  <Event1 @click="handler3"/>
  <hr>
  <Event2 @event2="handler4"/>

通过defineEmits接收自定义事件

let emit = defineEmits(['click'])

let emit = defineEmits(['event2']);

利用回调函数向父组件传值,其中第一个参数为自定义事件名,之后可带多个参数

const handler =()=>{
  emit('click','向父组件传值1')
}

const handler = () => {
  emit('event2','传给父组件2')
}

回调参数

父组件使用,用函数接收参数

const handler3 = (param) => {
  console.log('接收到子组件1的值:'+param)
}
const handler4 = (param) => {
  console.log('接收到子组件2的值:'+param)
}

自定义事件


三、全局事件总线(EventBus)

不提倡使用

Vue2中使用$bus可进行全局事件的通讯

使用步骤:

  • 创建vue实例
import Vue from 'vue'
export const bus = new Vue()
  • 在B组件想发送数据,定义this.$bus.$emit
import {bus} from "../../utils/bus.js"
// 这里自己修改路径,且bus.js文件中导出的是export所以使用import {bus}
...
// 发送数据
bus.$emit(参数1'定义一个方法名', 参数2'要发送的数据')
  • 在A组件想接收数据,定义this.$bus.$onthis.$bus.$off
import {bus} from "../../../utils/bus.js"
...
// 接收数据
bus.$on(参数1'$emit的方法名', 参数2:'function(value){
    // value是接收到的数据
}')
  • 清除
eventBus.$off('方法名', {})

在Vue3中没有全局事件总线,可通过插件mitt来实现全局总线事件,mitt不依赖 Vue 实例,所以可以跨框架使用,mitt里面封装了4个方法:

  • all:所有的全局事件
  • emit:发布事件
  • off:移除事件监听
  • on :接收事件

mitt方法

使用

  • 安装并创建文件挂载
pnpm add mitt

//引入mitt插件:mitt一个方法,触发执行会返回bus对象
import mitt from 'mitt';
//挂在在$bus变量上
const $bus = mitt();
//导出
export default $bus;
  • 组件发布事件
<script setup lang="ts">
import $bus from "../../bus";
const handler = () => {
  $bus.emit('car',{car:"法拉利"})
}
</script>

<template>
  <div class="child2">
    <p>我是子组件2</p>
    <button @click="handler">点击给兄弟组件1传值</button>
  </div>
  <div></div>
</template>
  • 组件使用接收
import $bus from '../../bus'
import {onMounted} from "vue";
//组件挂载完成时,为荡秋千组件绑定一个事件,接收兄弟组件传递的数据
onMounted(()=>{
  //第一个参数:事件类型
  //第二个参数:事件回调
  $bus.on('car',(car)=>{
    console.log(car)
  })
})

在这里插入图片描述

可以看到该插件使用map来存事件的,key就是事件名称,value则是一个回调函数,所以,all也继承了map的方法,可以通过其方法对事件进行操作。

//取消监听
$bus.off("car")
//如果发送了多个,可监听全部
$bus.on("*",(type,val) = >{
   console.log(type,val)   //type就是类型 之前注册的getDetail
})
//取消所有监听
$bus.all.clear();

四、v-model

在Vue3.3中,简化了子传父的代码,利用defineModel完成defineEmit的代码。

v-model为组件注册了自定义事件update:modelValue,并在props的setter调用了该事件modelValue,从而实现v-model的语法糖。

父组件代码:

<template>
  <div>
    {{msg}}
  </div>
  <Child1 v-model="msg"/>
</template>

<script setup>
import { ref } from 'vue'
import Child1 from "../04_v-model/Child1.vue";
// 第一次父组件传给子组件的值
const msg =ref(4)
</script>

子组件代码:

<template>
  <input type="text" v-model="msg">
</template>
<script setup>
import { defineModel } from 'vue'
let msg = defineModel('msg')
</script>

可以在一个组件上绑定多个v-model
相当于给组件绑定了 update:pageNoupdate:pageSize 的自定义事件

<Child2 v-model:pageNo="pageNo" v-model:pageSize="pageSize"/>

子组件代码

<script setup lang="ts">
import { defineModel } from 'vue'
let No = defineModel()

// 接收参数
let props = defineProps(['pageNo','pageSize'])
let page = defineEmits(['update:pageNo','update:pageSize'])

// 子组件回调传值
const headler = () => {
  page("update:pageNo",props.pageNo +6)
  page("update:pageSize",props.pageSize +6)
}
</script>

<template>
  <div class="child2">
    <p>同时绑定多个v-model</p>
    <div>{{props.pageNo}}</div>
    <div>{{props.pageSize}}</div>
    <Button @click="headler"></Button>
  </div>
  <div></div>
</template>

五、userAttrs

vue3框架提供一个方法useAttrs方法,它可以获取组件身上的属性与事件

该方法可以获取在组件 标签上的原生和自定义事件,

<HintButton type="primary" size="small" :icon="Edit"></HintButton>

userAttrs

例子:

父组件

<script setup lang="ts">
// vue3框架提供一个方法useAttrs方法,它可以获取组件身上的属性与事件
import {Edit} from '@element-plus/icons-vue'
import HintButton from "./HintButton.vue";

</script>

<template>
<div>
  <h1>userAttrs</h1>
  <el-button type="primary" size="small" :icon="Edit" ></el-button>
<!--  自定义组件-->
  <HintButton type="primary" size="small" :icon="Edit"></HintButton>
</div>
</template>

<style scoped>

</style>

子组件

<script setup lang="ts">
//引入useAttrs方法:获取标签上属性与事件
import {useAttrs} from "vue"; //返回对象
let $attrs = useAttrs();

// 如果使用props接收属性和属性值,则useAttrs是接收不到的
//因为props的优先级高于useAttrs
console.log($attrs)
</script>

<template>
<div>
  <el-button :="$attrs">子组件</el-button>
</div>
</template>

<style scoped>

</style>

被props获取的属性和属性值不会被useAttrs获取到


六、ref和$parent

通过ref和$parent可以获取DOM实例对象,就可以进行通讯了

ref和$parent获取到的DOM实例是获取不到内部定义的数据的,所以要通过 defineExpose 暴露出去

  • ref(父传子)

在子标签上添加ref

<Son ref="son"/>

再声明同名变量,此时son就是子组件实例

let son = ref()

在子组件中暴露需要传递的数据或者方法

defineExpose({
  money,
  fly
})

在父组件中使用

const handler = () => {
  money.value+=10;
  console.log(son)
  //操作子组件数据
  son.value.money-=10
  son.value.fly()
}
  • $parent(子传父)

在子组件中定义事件并传入 $parent 作为参数,此时$parent就是父组件实例

<button @click="handler($parent)">我是子组件2按钮</button>

const handler = ($parent) => {
  money.value+=1000
  console.log($parent)
  //操作父组件数据
  $parent.money-=1000
}

父组件对外暴露数据或者方法

//对外暴露
defineExpose({
  money
})

完整代码

父组件

<script setup lang="ts">
//ref:获取真实DOM结点,可以获取到子组件实例VC
//$parent:可以在子组件内部获取到父组件的实例
import {ref} from 'vue'
import Son from "./Son.vue";
import Daughter from "./Daughter.vue";
let money = ref(100000000)
//获取子组件实例
let son = ref()
const handler = () => {
  money.value+=10;
  console.log(son)
  //操作子组件数据
  son.value.money-=10
  son.value.fly()
}
//对外暴露
defineExpose({
  money
})

</script>

<template>
<div class="box">
  <h1>ref与$parent</h1>
  <h2>我是父组件:{{money}}</h2>
  <button @click="handler">增加父组件数值减少子组件数值</button>
  <hr>
<!--  ref形式-->
  <Son ref="son"/>
  <hr>
<!--  $parent形式-->
  <Daughter />
</div>
</template>

<style scoped>
.box{
  width: 100vw;
  height: 500px;
  background: skyblue;
}
</style>

ref子组件

<script setup lang="ts">
import {ref} from "vue";

let money = ref(666)

const fly = ()=>{
  console.log('我是子内部组件方法')
}
//组件内部数据对外是关闭的,其他组件不能访问
//如果想让外部访问需要通过defineExpose方法对外暴露
defineExpose({
  money,
  fly
})
</script>

<template>
<div class="son">
<h2>我是子组件:{{money}}</h2>
</div>
</template>

<style scoped>
.son{
  width: 300px;
  height: 200px;
  background: cyan;
}
</style>

$parent子组件

<script setup lang="ts">
import {ref} from "vue";

let money = ref(2000)
//事件回调传入参数 $parent 来获取父组件实例
//父组件必须将数据对外暴露
const handler = ($parent) => {
  money.value+=1000
  console.log($parent)
  //操作父组件数据
  $parent.money-=1000
}
</script>

<template>
<div class="dau">
  <h1>我是子组件2</h1>
  <button @click="handler($parent)">我是子组件2按钮</button>
</div>
</template>

<style scoped>
.dau{
  width: 300px;
  height: 300px;
  background: hotpink;
}
</style>

七、Provide与Inject

依赖注入
vue3 提供了provide(提供)与inject(注入),可以实现隔辈组件通讯
利用了原型链,在父组件provides上创建新对象:Object.create().parentProvides
子组件使用时在原型链上查找

  • provide是个方法提供两个参数,提供数据
    提供数据的key
    组件提供的数据
  • inject通过注入祖先提供的数据,利用key值来获取数据

注:后辈组件是可以修改祖先组件传过来的的值,并影响祖先组件的数据

个人建议:对于子改变父组件数据尽量不要用,保证数据的单向流动,防止父组件数据更改后影响其他组件
以下为提供的两个将数据变为非响应式数据的方法

  • toRaw:把一个响应式对象转化为普通对象
  • markRaw:把某个数据,标记为普通对象,当我们把它放到响应式对象中,也依然是非响应式的

在任意组件中注入数据

let car = ref<String>("大众")
type Car = {
  car:string
}
type CarData = {
  [index: number]:Car
}
let cars:CarData = reactive([{car:'大众'},{car:'宝马'}])
const TOKEN = Symbol() as InjectionKey<CarData>
provide(TOKEN,cars)

可以在其他组件中接收数据

<script setup lang="ts">
import {inject} from "vue";
let cars = inject<string>('TOKEN')
console.log(cars)
const hander=()=>{
  cars[0].car = '奔驰'
}
</script>

<template>
  <div class="child2">
    <h1>孙子组件</h1>
    <p v-for="item of cars">{{item.car}}</p>
    <button @click="hander">改变</button>
  </div>
</template>

如果接收的是个响应式数据可以在接收组件改变其数据


八、pinia

在vue3之前状态管理使用VueX来实现,现在官方更加推荐pinia在vue3中实现状态管理

vuex:集中式管理状态容器,可以实现任意组件之间的通讯 核心:
1、state 存储数据
2、mutations 唯一修改数据
3、actions 处理异步、处理业务
4、getters 计算属性
5、modules 模块式开发


pinia:集中式管理状态容器,可以实现任意组件之间的通讯 核心:
1、state 存储数据
2、actions 修改数据、处理异步、处理业务
3、getters 计算属性

利用createPinia方法创建大仓库
并对外暴露该仓库
在全局引入(文件位置:./src/store)

import {createPinia} from "pinia";
let store = createPinia()
export default store

选择式API仓库
defineStore方法定义小仓库,带两个参数
1、仓库名称
2、仓库配置对象
defineStore返回一个函数,让组件可以获取到仓库数据
存储数据:state
需对外暴露方法

import {defineStore} from "pinia";
let userInfoStore = defineStore("info",{
    state:()=>{
        return {
            count: 99,
            arr: [1,2,3,4,5,6,7,8,9,10]
        }
    },
    actions: {
        //内部没有context上下文对象
        //没有commit、没有mutations去修改数据
        updateNum(a:number,b:number){
            this.count+=(a+b)
        }
    },
    getters: {
        total() {
            let result:number = this.arr.reduce((prev,next)=>{
                return prev+next
            },0)
            return result
        }
    }
})
export default userInfoStore

定义组合式API仓库
务必返回一个对象:属性与方法可以提供给组件使用

import {defineStore} from "pinia";
import {computed, reactive} from "vue";
let userTodoStore = defineStore("todo",()=>{

    let todos = reactive([{id:1,title:'吃饭'},{id:2,title:'睡觉'}])
   let arr = reactive([1,2,3,4,5,6])
    const total = computed(()=>{
        return arr.reduce((prev,next)=>{
            return prev+next
        },0)
    })
    return {
        todos,
        total,
        updateTodo(){
            todos.push({id:3,title: '组合式API'})
        }
    }
})
export default userTodoStore

写法:
1、选择式
修改数据:

  • 使用返回的函数直接修改其属性
    infoStore.count++

  • 使用返回函数上的 $patch 方法
    infoStore.$patch({count:222})

  • 使用自定义方法,在actions中定义方法,可传参
    infoStore.updateNum()
    仓库:this.count++
    注:在方法内部要用this,this指向仓库对象

    函数上的方法

2、组合式
修改数据:

  • 使用返回的函数直接修改其属性
    todoStore.todos[0].title = ‘喝水’

  • 使用computed计算属性,将计算值返回就能获取
    const total = computed(()=>{
    return arr.reduce((prev,next)=>{
    return prev+next
    },0)
    })

  • 使用自定义方法,在return中定义方法,可传参
    updateTodo(){
    todos.push({id:3,title: ‘组合式API’})
    }
    注:在方法内部要用this,this指向仓库对象


九、slot插槽

插槽分为三种插槽:

  • 默认插槽
    在子组件直接使用slot标签<slot></slot>
    在父组件中子组件标签里添加传递的结构
  • 具名插槽
    在子组件直接使用slot标签<slot name="a"></slot>
    在父组件中子组件标签里添加template传递的结构,并用v-slot:(简写: #)指定是那个插槽
<template v-slot:a>
    <pre>早死晚死都得死</pre>
  </template>
  • 作用域插槽
    可传递数据的插槽,子组件可以将数据回传给父组件,父组件可以定义该数据以某种方式或者外观在子组件内部展示
    子组件可以在slot标签上传递数据<slot :row="item" :index="index"></slot>
    父组件在template用v-slot用接收数据并定义子组件内部显示方式
 <template v-slot="{row,index}">
          <span :style="{color:row.done?'green':'red'}">{{row.title}}--{{index}}</span>
        </template>

总结

本文主要记录了vue3中九种组件通讯的方法与示例。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1004288.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MCU主频 服务器台式机主频 处理器主频那些事

几十M级别的 几百M级别的 几个G级别 早期的典型的51单片机外部接12MHz晶振&#xff0c;内部电路对12MHz的原始时钟进行12分频变成1MHz的时钟给CPU&#xff0c;所以早期典型的51内核的主频是1MHz。后来工艺改良了单片机也设计也改良了&#xff0c;CPU可以耐受的主频提升了&am…

【Unity3D】UI Toolkit数据动态绑定

1 前言 本文将实现 cvs 表格数据与 UI Toolkit 元素的动态绑定。 如果读者对 UI Toolkit 不是太了解&#xff0c;可以参考以下内容。 UI Toolkit简介UI Toolkit容器UI Toolkit元素UI Toolkit样式选择器UI Toolkit自定义元素 本文完整资源见→UI Toolkit数据动态绑定。 2 数据…

如何制作医疗科普宣传片

科普宣传片通过视觉呈现、简化浓缩、故事叙述、情感引导等手段&#xff0c;将科学知识生动地传达给观众&#xff0c;覆盖广泛的传播渠道使其影响力更大。制作医疗科普宣传片需要综合考虑目标受众、内容传递、专业性和吸引力等因素。下面是一些制作医疗科普宣传片的步骤和建议&a…

在滴滴和字节划水四年半,太真实了...

先简单交代一下吧&#xff0c;沅哥是某不知名211的本硕&#xff0c;18年毕业加入滴滴&#xff0c;之后跳槽到了头条&#xff0c;一直从事测试开发相关的工作。之前没有实习经历&#xff0c;算是四年半的工作经验吧。 这四年半之间他完成了一次晋升&#xff0c;换了一家公司&am…

【数据结构-二叉树】二叉树

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…

什么是软件测试?5分钟带你快速了解!

经常有人问我&#xff0c;你的公司是做什么的&#xff1f;我回答“软件测试”&#xff0c;看着对方一脸的迷茫。何为软件测试&#xff1f;软件测试究竟测试什么&#xff1f; 一、软件测试的定义和意义 软件测试是伴随着软件工程的重要组成部分&#xff0c;是软件质量保证的重…

在python程序中用windows的icon

这个exe的弹窗功能会使用到一个ico文件&#xff0c;如图&#xff1a; 用软件GreenfishIconEditorProPortable或者使用在线软件将你需要的图片制作成windows的icon 用程序将ico文件生成文本文件 import base64picture_name "logo.ico" open_pic open("%s…

VirtualBox宿主机和虚拟机文件互传设置

一、如图1、2、3步骤&#xff0c;设置共享粘贴板和拖放为双向 二、 在启动的虚拟机设置的里面&#xff0c;安装增强插件&#xff0c;然后重启虚拟机。 三、在网络位置就可以看到了

十种数据库缓存相关的技术和机制

数据库的缓存 -- 通过将数据库中的数据或结果集保存在内存或其他快速访问的介质中&#xff0c;能够加快查询响应&#xff0c;减少对磁盘或远程服务器的访问&#xff0c;降低资源消耗。 根据缓存的位置、内容、粒度、更新方式等不同&#xff0c;数据库缓存技术有多种类型和策略。…

NAT(网络地址转换)

文章目录 一、产生背景二、公有地址和私有地址三、定义四、分类五、常用命令 首先可以看下思维导图&#xff0c;以便更好的理解接下来的内容。 一、产生背景 IPv4公网地址资源耗尽&#xff1a; 由于IPv4地址空间有限&#xff0c;公网IPv4地址资源逐渐耗尽&#xff0c;导致难以分…

图像尺寸测量仪:解析适用零部件与应用领域

图像尺寸测量仪也叫一键测量仪器&#xff0c;全自动闪测仪等&#xff0c;是一种精密二次元测量仪器。它能够精确测量各种零部件的形状和尺寸&#xff0c;核心优势在于测量大批量小型精密零部件&#xff0c;这对于质量控制和生产流程的优化至关重要。 图像尺寸测量仪适用于哪些…

简记——示波器测量上电冲击、电源纹波方法

一、示波器测量上电冲击 1、用稳压电源给电路板供电&#xff0c;在正极上串联一个20欧的电阻&#xff0c;设置示波器如下&#xff1a; 类型&#xff1a; “边沿触发”&#xff0c; 斜率&#xff1a; “上升”&#xff0c; 触发方式&#xff1a; …

工信部短信核验常见问题

1、收不到管局发送的核验短信怎么办&#xff1f; 如果你未收到管局发送的核验短信&#xff0c;请按以下信息进行排查。检查信息是否被手机管家识别为垃圾或骚扰短信被拦截&#xff0c;确认能不能正常接收其他短信。检查手机是否欠费停机。建议你将手机卡插入其他手机&#xff…

unity 接收拼接数据进行纹理替换且保存相机纹理到rtsp server(一)

1 rtsp 协议后编码解码 rtsp协议的问题就是&#xff0c;拼接完成后&#xff0c;还需要编码&#xff0c;而unity里面再需要解码&#xff0c;需要的过程多了一步编码再解码&#xff0c;大大加重了 2 rtsp 协议后轻量编码 rtsp协议使用mjpeg进行图片传输。why&#xff1f;这样做…

C++信息学奥赛1170:计算2的N次方

#include <iostream> #include <string> #include <cstring>using namespace std;int main() {int n;cin >> n; // 输入一个整数nint arr[100];memset(arr, -1, sizeof(arr)); // 将数组arr的元素初始化为-1&#xff0c;sizeof(arr)表示arr数组的字节…

没有办法destory 一部分array

C Deleting part of dynamic array - Stack Overflow

C++之std::enable_shared_from_this实例(一百九十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

如何利用在线考试系统优化答题过程

随着技术的不断进步&#xff0c;越来越多的考试机构和教育机构开始采用在线考试系统进行评估和测验。使用在线考试系统可以极大地提升答题的效率和准确性。 准备阶段 在开始答题前&#xff0c;有一些准备工作可以帮助优化答题过程。 要仔细阅读考试要求和题目说明。这些说明…

JS中应该注意的点

本帖子记录在使用前端时遇到的一些小点。 1.html()和text()和val()的使用及区别 1.1 val() val&#xff08;&#xff09;是对于单标签元素的值&#xff0c;其中一个很重要的特性是value"" Value:<input id"input" type"text" value"LO…

postman导入json脚本文件(Collection v1.0、Collection v2.0)

1. 以postman v8.5.1 版本为例 2. 在postman v5.0.2 低版本中导出json脚本文件, 请选择Collection v2.0 Export - Collection v2 3. 在postman v8.5.1 版本 导入 json脚本文件 Import - Collection v2 - Export - Import