vue3组件通信的九大方式(props,自定义事件,$bus,v-model,useAttrs,ref和$parent,provide与inject,pinia,slot)

vue3组件通信的九大方式:props,自定义事件,$bus,v-model,useAttrs,ref和$parent,provide与inject,pinia,slot

  • 一、props
  • 二、自定义事件
    • 2.1 原生DOM事件
    • 2.2 自定义事件
      • 2.2.1 vue3原生DOM事件
      • 2.2.2 vue3 自定义事件传参
  • 三、全局事件$bus
  • 四、v-model
    • 4.1 底层实现
    • 4.2 简写 (v-model)
    • 4.3 v-model绑定多个参数
  • 五、useAttrs
  • 六、ref和$parent
    • 6.1 ref的使用
    • 6.2 $parent的使用
  • 七、provide与inject
  • 八、pinia
    • 8.1 选项式API写法
    • 8.2 组合式API写法
  • 九、slot
    • 9.1 默认插槽
    • 9.2 具名插槽
    • 9.3 作用域插槽
  • 以上基础资料根据尚硅谷实战硅谷甄选整理而得,[视频地址](https://www.bilibili.com/video/BV1Xh411V7b5?p=1&vd_source=8e12a7a04b1332f06a88f6c51979e883),讲解的非常棒!!!
  • 以上完整代码[码云地址](https://gitee.com/qq95765497/vue3-communication.git)

一、props

  • props可以实现父子组件通信,在vue3中我们可以通过defineProps获取父组件传递的数据
  • 在组件内部不需要引入defineProps方法可以直接使用
  • props是只读属性!!!

父页面向子组件传值

 <Child info="我是曹操" :money="money">Child>

子组件获取父组件传值:方式一

let props = defineProps({
  info:{
   type:String,//接受的数据类型
   default:'默认参数',//接受默认数据
  },
  money:{
   type:Number,
   default:0
}})

子组件获取父组件传值:方式二

let props = defineProps(["info",'money']);

二、自定义事件

  • 在vue框架中事件分为两种:一种是原生的DOM事件,另外一种自定义事件
  • 原生DOM事件可以让用户与网页进行交互,比如click、dbclick、change...
  • 自定义事件可以实现子组件给父组件传递数据

2.1 原生DOM事件

示例代码

  • 当前代码级给pre标签绑定原生DOM事件点击事件,默认会给事件回调注入event事件对象
<pre @click="handler">
      大江东去浪淘尽,千古分流人物
pre>
const handler = ($event) => {
  //$event即是事件对象
  console.log('点击事件')
  alert('点击事件')
}
  • 回调事件传递多个参数
<button @click="handler1(1, 2, 3, 4, $event)">点击我传递多个参数button>
const handler1 = (a, b, c, d, e) => {
  console.log('事件回调2', a, b, c, d, e)
}

2.2 自定义事件

2.2.1 vue3原生DOM事件

  • vue2框架当中 :下边写法是自定义事件,可以通过.native修饰符变为原生Dom事件
  • vue3框架中下边写法其实就是原生Dom事件
  • vue3:原生dom事件不管放在标签身上,组件标签身上都是原生dom事件

示例代码 父组件

 <Event1 @click="handler2">Event1>
const handler2 = () => {
  console.log('事件回调3')
}

示例代码 子组件 Event1

<template>
  <div class="son">
      <p>我是子组件1</p>
      <button>点击我也执行</button>
  </div>
</template>
<style scoped>
.son{
  width: 400px;
  height: 200px;
  background: skyblue;
}
</style>

实现效果

  • 无论点击外层容器还是内置按钮都会触发组件上绑定的事件
    vue3组件通信的九大方式(props,自定义事件,$bus,v-model,useAttrs,ref和$parent,provide与inject,pinia,slot)_第1张图片

2.2.2 vue3 自定义事件传参

  • 绑定自定义事件 实现子组件给父组件传递数据

示例代码 父页面

 <Event2 @xxx="handler3" @click="handler4">Event2>
//事件回调--4
const handler3 = (a, b) => {
  console.log('事件回调4', a, b)
}

//事件回调--5
const handler4 = (a) => {
  console.log('事件回调5', a)

  alert('事件回调5')
}

示例代码 子组件

  • 使用defineEmits 进行调用父页面方法并传递参数
  • defineEmits 无需引入
  • 正常说组件标签书写@click应该为原生DOM事件,但是如果子组件内部通过defineEmits定义就变为自定义事件
<template>
  <div class="child">
    <p>我是子组件2p>
    <button @click="handler">点击我触发自定义事件xxxbutton>
    <button @click="$emit('click', 'ak47')">点击我触发自定义事件clickbutton>
  div>
template>

<script setup lang="ts">
//步骤一
//利用 defineEmits 方法返回函数触发自定义事件
//defineEmits 不需要引入直接使用
//此处写上click 即视为自定义事件
let $emit = defineEmits(['xxx', 'click'])

//按钮点击回调
const handler = () => {
  //步骤二
  //第一个参数:事件类型   第二个参数|第三个|...注入的数据
  $emit('xxx', '导弹', '火箭')
}
script>

<style scoped>
.child {
  width: 400px;
  height: 200px;
  background: pink;
}
style>

三、全局事件$bus

  • 全局事件总线可以实现任意组件通信,在vue2中可以根据VM与VC关系推出全局事件总线
  • 但是在vue3中没有Vue构造函数,也就没有Vue.prototype.以及组合式API写法没有this
  • 想在Vue3中使用全局事件总线功能可以使用插件mitt实现
  • mitt官网
  • 实现功能: 父组件包含 A B两个组件,实现A组件传值 B组件接值

示例代码 封装mitt方法

//创建bus文件夹(一般放在utils中),创建index.ts
//引入mitt插件:mitt一个方法,方法执行会返回bus对象
import mitt from 'mitt';
const $bus = mitt();
export default $bus;

示例代码 父页面

<template>
  <div class="box">
    <h1>全局事件总线$bush1>
    <hr />
    <div class="container">
      <Child1>Child1>
      <Child2>Child2>
    div>
  div>
template>

<script setup lang="ts">
//引入子组件
import Child1 from "./Child1.vue";
import Child2 from "./Child2.vue";
script>

<style scoped>
.box {
  width: 100vw;
  height: 400px;
  background: yellowgreen;
}
.container{
  display: flex;
  justify-content: space-between;
}
style>

示例代码 Child2组件 (传值)

<template>
  <div class="child2">
    <h2>我是子组件2:曹丕h2>
    <button @click="handler">点击我给兄弟送一台法拉利button>
  div>
template>

<script setup lang="ts">
//引入$bus对象
import $bus from '../../bus'
//点击按钮回调
const handler = () => {
  $bus.emit('Car', { car: '法拉利' })
}
script>

<style scoped>
.child2 {
  width: 300px;
  height: 300px;
  background: skyblue;
}
style>

示例代码 Child1组件 (接值)

<template>
  <div class="child1">
    <h3>我是子组件1:曹植h3>
  div>
template>

<script setup lang="ts">
import $bus from '../../bus'
//组合式API函数
import { onMounted } from 'vue'
//当组件挂载完毕的时候 当前组件绑定一个事件  接收将来兄弟组件传递的数据
onMounted(() => {
  //第一个参数:事件类型  第二个参数:事件回调
  $bus.on('Car', (car) => {
    console.log('bus接收', car)
  })
})
script>

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

vue3组件通信的九大方式(props,自定义事件,$bus,v-model,useAttrs,ref和$parent,provide与inject,pinia,slot)_第2张图片

四、v-model

  • v-model指令可是收集表单数据(数据双向绑定),除此之外它也可以实现父子组件数据同步
  • 而v-model实指利用props[modelValue]与自定义事件[update:modelValue]实现的

4.1 底层实现

  • props[modelValue]+[update:modelValue]

父页面代码


    <Child :modelValue="money" @update:modelValue="handler">Child>
    
//自定义事件的回调
const handler = (value: number) => {
  //接收子组件传递过来的数据
  money.value = value
}

子组件代码

<template>
  <div class="child">
    <h3>钱数:{{ modelValue }}h3>
    <button @click="handler">父子组件数据同步button>
  div>
template>

<script setup lang="ts">
//接收props
let props = defineProps(['modelValue'])
let $emit = defineEmits(['update:modelValue'])
//子组件内部按钮的点击回调
const handler = () => {
  //触发自定义事件
  $emit('update:modelValue', props.modelValue + 1000)
}
script>

<style scoped>
.child {
  width: 600px;
  height: 300px;
  background: skyblue;
}
style>

4.2 简写 (v-model)

上诉代码中父组件 中更改为如下


    
    
    <Child v-model="money">Child>

4.3 v-model绑定多个参数

父页面

 <Child1 v-model:pageNo="pageNo" v-model:pageSize="pageSize">Child1>

子组件

<template>
  <div class="child2">
    <h1>同时绑定多个v-modelh1>
    <button @click="handler">pageNo{{ pageNo }}button>
    <button @click="$emit('update:pageSize', pageSize + 10)">
      pageSize{{ pageSize }}
    button>
  div>
template>

<script setup lang="ts">
let props = defineProps(['pageNo', 'pageSize'])
let $emit = defineEmits(['update:pageNo', 'update:pageSize'])
//第一个按钮的事件回调
const handler = () => {
  $emit('update:pageNo', props.pageNo + 3)
}
script>

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

五、useAttrs

  • 在Vue3中可以利用useAttrs方法获取组件的属性与事件(包含:原生DOM事件或者自定义事件)
  • 子组件内部可以通过useAttrs方法获取组件属性与事件类似于props,可以接受父组件传递过来的属性与属性值。需要注意如果defineProps接受了某一个属性,useAttrs方法返回的对象身上就没有相应属性与属性值
  • v-bind绑定多个值时可简写 :="{ a: 1, b: 2 }"

父页面 代码

<template>
  <div>
    <h1>useAttrsh1>
    <el-button type="primary" size="small" :icon="Edit">el-button>
    
    <HintButton
      type="primary"
      size="small"
      :icon="Edit"
      title="编辑按钮"
      @click="handler"
    >HintButton>
  div>
template>

<script setup lang="ts">
//vue3框架提供一个方法useAttrs方法,它可以获取组件身上的属性与事件!!!
//图标组件
import { Edit } from '@element-plus/icons-vue'
import HintButton from './HintButton.vue'
//按钮点击的回调
const handler = () => {
  alert(12306)
}
script>

子页面代码

<template>
  <div :title="title">
    <el-button :="$attrs">el-button>
    
    <h1 v-bind="{ a: 1, b: 2 }">h1>
  div>
template>

<script setup lang="ts">
//接收父组件传递过来的数据
// defineProps(['type', 'size', 'icon'])
//引入useAttrs方法:获取组件标签身上属性与事件
import { useAttrs } from 'vue'
console.log(useAttrs)
let $attrs = useAttrs()

//万一用props接受title
let props = defineProps(['title'])
console.log(props) //有title属性
console.log($attrs) //没有title属性  props优先级更高
//props和useAttrs方法都可以获取父组件传递过来的属性与属性值
//但是props接受了useAttrs方法就获取不到了
script>

<style scoped>style>

六、ref和$parent

  • 在父组件内部通过ref获取子组件实例VC,那么子组件内部的方法与响应式数据父组件可以使用的
  • $parent:可以在子组件内部获取到父组件的实例

6.1 ref的使用

实现以下效果一

  • 点击父页面借10元按钮 父页面金额+10 子组件中金额-10
    vue3组件通信的九大方式(props,自定义事件,$bus,v-model,useAttrs,ref和$parent,provide与inject,pinia,slot)_第3张图片

父页面 代码 组件绑定ref

<template>
  <div class="box">
    <h1>我是父亲曹操:{{ money }}h1>
    <button @click="handler()">找我的儿子曹植借10元button>
    <hr />
    <Son ref="son">Son>
  div>
template>

<script setup lang="ts">
//ref:可以获取真实的DOM节点,可以获取到子组件实例VC
//$parent:可以在子组件内部获取到父组件的实例
//引入子组件
import Son from './Son.vue'
import { ref } from 'vue'
//父组件钱数
let money = ref(100000000)
//获取子组件的实例
let son = ref()

//父组件内部按钮点击回调
const handler = () => {
  money.value += 10
  console.log(son.value.money)
  //儿子的钱数-10
  son.value.money -= 10
  //调用子组件的方法
  son.value.fly()
}
script>

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

子组件代码 暴漏 变量

<template>
  <div class="son">
    <h3>我是子组件:曹植{{ money }}h3>
  div>
template>

<script setup lang="ts">
import { ref } from 'vue'
//儿子钱数
let money = ref(666)
const fly = () => {
  console.log('我可以飞')
}
//组件内部的数据对外关闭的  别人不能访问
//如果想让外部访问 需要通过defineExpose  对外暴漏
defineExpose({
  money,
  fly,
})
script>

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

6.2 $parent的使用

实现效果

  • 点击子组件按钮,子组件金额-10000 父组件金额+10000

vue3组件通信的九大方式(props,自定义事件,$bus,v-model,useAttrs,ref和$parent,provide与inject,pinia,slot)_第4张图片

父页面 代码 暴漏变量

<template>
  <div class="box">
    <h1>我是父亲曹操:{{ money }}h1>
    <hr />
    <Dau>Dau>
  div>
template>

<script setup lang="ts">
//ref:可以获取真实的DOM节点,可以获取到子组件实例VC
//$parent:可以在子组件内部获取到父组件的实例
import Dau from './Daughter.vue'
import { ref } from 'vue'
//父组件钱数
let money = ref(100000000)

//对外暴漏
defineExpose({
  money,
})
script>

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

子组件示例代码 $parent

<template>
  <div class="dau">
    <h1>我是闺女曹杰{{ money }}h1>
    <button @click="handler($parent)">点击我爸爸给我10000元button>
  div>
template>

<script setup lang="ts">
import { ref } from 'vue'
//闺女钱数
let money = ref(999999)
//闺女按钮点击回调
const handler = ($parent: any) => {
  console.log($parent)
  money.value -= 10000
  $parent.money += 10000
}
script>

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

七、provide与inject

  • vue3提供两个方法provide(提供)与inject(注入),可以实现隔辈组件传递参数
  • provide方法用于提供数据,此方法执需要传递两个参数,分别提供数据的key与提供数据value
<script setup lang="ts">
import {provide} from 'vue'
provide('token','admin_token');
</script>
  • 后代组件可以通过inject方法获取数据,通过key获取存储的数值
<script setup lang="ts">
import {inject} from 'vue'
let token = inject('token');
</script>

示例场景

-实现功能 父页面包含子组件 子组件中又包含孙子组件,父页面和孙子组件互相传值
vue3组件通信的九大方式(props,自定义事件,$bus,v-model,useAttrs,ref和$parent,provide与inject,pinia,slot)_第5张图片

父页面 代码

<template>
  <div class="box">
    <h1>Provide与Inject{{ car }}h1>
    <hr />
    <Child>Child>
  div>
template>

<script setup lang="ts">
import Child from './Child.vue'
//vue3提供provide(提供)与inject(注入),可以实现隔辈组件传递数据
import { ref, provide } from 'vue'
let car = ref('法拉利')

//祖先组件给后代组件 提供数据
//provide参数1 提供的数据key
//provide参数2 提供的数据value
provide('Token', car)
script>

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

子组件代码

<template>
  <div class="child">
     <h1>我是子组件1h1>
     <Child>Child>
  div>
template>

<script setup lang="ts">
import Child from './GrandChild.vue';
script>

<style scoped>
.child{
  width: 300px;
  height: 400px;
  background: yellowgreen;
}
style>

孙子组件 代码

<template>
  <div class="child1">
    <h1>孙子组件h1>
    <p>{{ car }}p>
    <button @click="updateCar">更新数据button>
  div>
template>

<script setup lang="ts">
import { inject } from 'vue'
//注入祖先组件提供的数据
//需要参数:即为祖先提供数据的key
let car = inject('Token')
console.log(car)
const updateCar = () => {
  car.value = '自行车'
}
script>

<style scoped>
.child1 {
  width: 200px;
  height: 200px;
  background: red;
}
style>

八、pinia

  • pinia官网
  • pinia也是集中式管理状态容器,类似于vuex。
  • 核心概念 state actions getters
  • pinia写法 :选择式api 组合式api

创建store文件夹=>index.ts 代码如下

//创建大仓库
import { createPinia } from 'pinia';
//createPinia方法可以用于创建大仓库
let store = createPinia();
//对外暴露,安装仓库
export default store;

在main.ts中统一挂载

//引入仓库
import store from './store'
// 创建app
const app = createApp(App)
app.use(store)

父页面代码

<template>
  <div class="box">
    <h1>piniah1>
    <div class="container">
      <Child>Child>
      <Child1>Child1>
    div>
  div>
template>

<script setup lang="ts">
import Child from './Child.vue'
import Child1 from './Child1.vue'
//vuex:集中式管理状态容器 可以实现任意组件之间的通信
//核心概念:state mutations actions getters modules

//pinia:集中式管理状态容器 可以实现任意组件之间的通信
//核心概念 state actions getters
//pinia写法 :选择式api 组合式api
script>

<style scoped>
.box {
  width: 600px;
  height: 400px;
  background: skyblue;
}
.container {
  display: flex;
}
style>

8.1 选项式API写法

在store文件夹下=>创建modules文件夹=>创建info.ts

//定义info小仓库
import { defineStore } from "pinia";
//第一个仓库:小仓库名字  第二个参数:小仓库配置对象
//defineStore方法执行会返回一个函数,函数作用就是让组件可以获取到仓库数据
let useInfoStore = defineStore("info", {
    //存储数据:state
    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;
        }
    },
    getters: {
        total() {
            let result:any = this.arr.reduce((prev: number, next: number) => {
                return prev + next;
            }, 0);
            return result;
        }
    }
});
//对外暴露方法
export default useInfoStore;

子组件1 代码 child

<template>
  <div class="child">
    <h1>{{ infoStore.count }}---{{infoStore.total}}h1>
    <button @click="updateCount">点击我修改仓库数据button>
  div>
template>

<script setup lang="ts">
import useInfoStore from "../../store/modules/info";
//获取小仓库对象
let infoStore = useInfoStore();
console.log(infoStore);
//修改数据方法
const updateCount = () => {
  //仓库调用自身的方法去修改仓库的数据
  infoStore.updateNum(66,77);
};
script>

<style scoped>
.child {
  width: 200px;
  height: 200px;
  background: yellowgreen;
}
style>

8.2 组合式API写法

在store文件夹下=>创建modules文件夹=>创建todo.ts

//定义组合式API仓库
import { defineStore } from "pinia";
import { ref, computed,watch} from 'vue';
//创建小仓库
let useTodoStore = defineStore('todo', () => {
    let todos = ref([{ id: 1, title: '吃饭' }, { id: 2, title: '睡觉' }, { id: 3, title: '打豆豆' }]);
    let arr = ref([1,2,3,4,5]);

    const total = computed(() => {
        return arr.value.reduce((prev, next) => {
            return prev + next;
        }, 0)
    })
    //务必要返回一个对象:属性与方法可以提供给组件使用
    return {
        todos,
        arr,
        total,
        updateTodo() {
            todos.value.push({ id: 4, title: '组合式API方法' });
        }
    }
});

export default useTodoStore;

子组件2 代码 child1

<template>
  <div class="child1">
    {{ infoStore.count }}
    <p @click="updateTodo">{{ todoStore.arr }}{{todoStore.total}}</p>
  </div>
</template>

<script setup lang="ts">
import useInfoStore from "../../store/modules/info";
//获取小仓库对象
let infoStore = useInfoStore();

//引入组合式API函数仓库
import useTodoStore from "../../store/modules/todo";
let todoStore = useTodoStore();

//点击p段落去修改仓库的数据
const updateTodo = () => {
  todoStore.updateTodo();
};
</script>

<style scoped>
.child1 {
  width: 200px;
  height: 200px;
  background: hotpink;
}
</style>

九、slot

  • 插槽:默认插槽、具名插槽、作用域插槽可以实现父子组件通信

9.1 默认插槽

在子组件内部的模板中书写slot全局组件标签

<template>
  <div>
    <slot>slot>
  div>
template>
<script setup lang="ts">
script>
<style scoped>
style>

在父组件内部提供结构:Todo即为子组件,在父组件内部使用的时候,在双标签内部书写结构传递给子组件
注意开发项目的时候默认插槽一般只有一个

<Todo>
  <h1>我是默认插槽填充的结构h1>
Todo>

9.2 具名插槽

插槽带有名字在组件内部留多个指定名字的插槽
下面是一个子组件内部,模板中留两个插槽

<template>
  <div>
    <h1>todoh1>
    <slot name="a">slot>
    <slot name="b">slot>
  div>
template>
<script setup lang="ts">
script>

<style scoped>
style>

父组件内部向指定的具名插槽传递结构。需要注意v-slot:可以替换为#

<template>
  <div>
    <h1>sloth1>
    <Todo>
      <template v-slot:a>  //可以用#a替换
        <div>填入组件A部分的结构div>
      template>
      <template v-slot:b>//可以用#b替换
        <div>填入组件B部分的结构div>
      template>
    Todo>
  div>
template>

<script setup lang="ts">
import Todo from "./Todo.vue";
script>
<style scoped>
style>

9.3 作用域插槽

作用域插槽:可以理解为,子组件数据由父组件提供,但是子组件内部决定不了自身结构与外观(样式)

子组件Todo代码如下

<template>
  <div>
    <h1>todoh1>
    <ul>
     
      <li v-for="(item,index) in todos" :key="item.id">
         
         <slot :$row="item" :$index="index">slot>
      li>
    ul>
  div>
template>
<script setup lang="ts">
defineProps(['todos']);//接受父组件传递过来的数据
script>
<style scoped>
style>

父组件内部代码如下

<template>
  <div>
    <h1>sloth1>
    <Todo :todos="todos">
      <template v-slot="{$row,$index}">
         
         <span :style="{color:$row.done?'green':'red'}">{{$row.title}}span>
      template>
    Todo>
  div>
template>

<script setup lang="ts">
import Todo from "./Todo.vue";
import { ref } from "vue";
//父组件内部数据
let todos = ref([
  { id: 1, title: "吃饭", done: true },
  { id: 2, title: "睡觉", done: false },
  { id: 3, title: "打豆豆", done: true },
]);
script>
<style scoped>
style>

以上基础资料根据尚硅谷实战硅谷甄选整理而得,视频地址,讲解的非常棒!!!

以上完整代码码云地址

你可能感兴趣的:(vue3,javascript,vue.js,前端)