Vue3的组件通信汇总

目录

自定义属性props(父向子)

自定义事件(子向父)

ref获取子组件实例

$parent获取父组件实例

作用域插槽向父组件传值

兄弟组件传值

父子双向数据绑定

useAttrs获取父组件传来的属性和事件

深层组件传值

Pinia实现任意组件之间通信

扫描二维码关注公众号,回复: 15175350 查看本文章

  • 首先,我们要明白,数据流是自顶向下的,也就是说,只能上层的修改下层的,不能下层的修改上层的(正确的做法,应该是下层的通知上层的,让上层的去修改)

自定义属性props(父向子)

  • 可以用于父向子传值
  • vue3中,子组件用 defineProps这个hook来接收父组件传来的数据。可以是一个数组,也可以是一个对象

  • defineProps不需要导入,defineProps接收到的数据,可以直接进行使用

父组件

<template>
  <div class="father">
    <h1>father</h1>
    <Son :count="count"></Son>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import Son from './Son.vue'

const count = ref(100)
</script>

<style lang="scss" scoped>
.father {
  height: 300px;
  background-color: pink;
  padding: 20px;
}
</style>

子组件

<template>
  <div class="son">
    <h3>son</h3>
    <p>父组件传来的数据: {
   
   { count }}</p>
  </div>
</template>

<script setup>
// 1. 数组
// const props = defineProps(['count'])
// 2. 对象,可以对父组件传来的数据进行一下校验
const props = defineProps({
  count: {
    type: Number,
    default: 0,
  },
})
</script>

<style lang="scss" scoped>
.son {
  height: 100px;
  background-color: #91beff;
}
</style>

自定义事件(子向父)

可以用于子向父传值

  1. 子组件通过emit向父组件传递一个自定义事件

  2. 父组件,在子组件标签上接收自定义事件,并声明处理函数

子组件

<template>
  <div class="son">
    <h3>son</h3>
    <button @click="sendData">点击向父组件传值</button>
  </div>
</template>

<script setup>
// 子组件通过 defineEmits 这个hook声明自定义事件
const $emit = defineEmits(['sendData'])

const sendData = () => {
  // 1. 子组件通过 emit 向父组件传递一个自定义事件(第一个参数是自定义事件名,第二个参数是传递的参数)
  $emit('sendData', 999)
}
</script>

<style lang="scss" scoped>
.son {
  height: 100px;
  background-color: #91beff;
}
</style>

父组件

<template>
  <div class="father">
    <h1>father --- 接收到的子组件传过来的值:{
   
   { count }}</h1>
    <!-- 2. 父组件,在子组件标签上接收自定义事件,并声明处理函数 -->
    <Son @sendData="sendData"></Son>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import Son from './Son.vue'

let count = ref(0)

// 3. 处理函数,如果子组件传递了参数,就可以写形参进行接收
const sendData = (val) => {
  console.log(val) // 接收到的子组件的参数
  // 可以把参数转存到声明的变量中,这样就可以直接使用了
  count.value = val
}
</script>

<style lang="scss" scoped>
.father {
  height: 300px;
  background-color: pink;
  padding: 20px;
}
</style>

补充:vue2和3的自定义事件

  • vue2中,如果在组件标签上写clickmouseEnterfocus等等这些原生的事件,它会以为就是自定义事件,不会以为是原生事件。如果想要它以原生的事件执行,则需要添加.native修饰符

<组件名 @click.native = 'handleClick'></组件名>

  • vue3,如果绑定原生事件,它就会以为就是原生事件。只要在子组件内,条件满足就会触发

<组件名 @click = 'handleClick'></组件名>

ref获取子组件实例

  • ref 可以获取真实的DOM节点,也可以获取组件实例。获取子组件实例后,就可以通过子组件实例,直接修改和使用子组件定义的变量,也可以直接调用子组件的方法

  • 组件内部的数据是封闭保护的,如果向让外界使用,可以使用defineExpose这个hook进行暴露

父组件

<template>
  <main class="father">
    <h1>father</h1>
    <Son ref="son"></Son>
  </main>
</template>

<script setup>
import { onMounted, ref } from 'vue'
import Son from './Son.vue'

// 子组件的实例化
let son = ref()
// 只有挂载到父组件之后,才能使用子组件的属性和方法
onMounted(() => {
  console.log(son.value.num)
})
</script>

<style>
.father {
  height: 400px;
  background-color: pink;
  padding: 20px;
}
</style>

子组件

<template>
  <div class="son">
    <h2>son</h2>
  </div>
</template>

<script setup>
import { ref } from 'vue'

let num = ref(10)
// 需要使用 defineExpose 进行对外暴露(方法和属性都可以)
defineExpose({
  num,
})
</script>

<style>
.son {
  height: 200px;
  background-color: skyblue;
}
</style>

$parent获取父组件实例

  • 可以在子组件中通过$parentz这个参数获取父组件的实例

  • 组件内部的数据是封闭保护的,如果向让外界使用,可以使用defineExpose这个hook进行暴露

子组件

<template>
  <div class="son">
    <h2>son</h2>
    <button @click="getParentData($parent)">获取父组件的数据</button>
  </div>
</template>

<script setup>
const getParentData = (parent) => {
  console.log('获取到了父组件实例', parent)
}
</script>

<style>
.son {
  height: 200px;
  background-color: skyblue;
}
</style>

父组件

<template>
  <main class="father">
    <h1>father</h1>
    <Son></Son>
  </main>
</template>

<script setup>
import { ref } from 'vue'
import Son from './Son.vue'

let count = ref(100)

// 需要使用 defineExpose 进行对外暴露(方法和属性都可以)
defineExpose({
  count,
})
</script>

<style>
.father {
  height: 400px;
  background-color: pink;
  padding: 20px;
}
</style>

作用域插槽向父组件传值

  • 子组件可以利用作用域插槽向父组件传值

  • 作用域插槽就是可以传递数据的插槽,子组件可以将数据回传给父组件,父组件可以决定这些回传的数据,是以何种结构或者外观在子组件内部去展示

子组件

<template>
    <div class="son">
        <h2>son</h2>
        <p>--------默认插槽---------</p>
        <slot></slot>
        <p>--------具名插槽。name指定插槽的名字---------</p>
        <slot name="superman"></slot>
        <p>--------作用域插槽。通过属性绑定,向父组件提供数据---------</p>
        <slot name="dc" :list="list"></slot>
    </div>
</template>
  

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

const list = ref([
    {
        name: '张三',
        age: 21,
        color: 'pink'
    },
    {
        name: '里斯',
        age: 22,
        color: 'skyblue'
    },
    {
        name: '王五',
        age: 23,
        color: 'hotpink'
    },
])
</script>

<style>
.son {
    height: 150px;
    background-color: skyblue;
}
</style>

父组件

<template>
  <main class="father">
    <h1>father</h1>
    <Son>
      <p>默认插槽里面的内容</p>
      <template #superman>具名插槽中的内容</template>
      <template #dc="{ list }">
        <ul>
          <li v-for="i in list" :key="i.name" :style="{ backgroundColor: i.color }">
            {
   
   { i.name }} -- {
   
   { i.age }}
          </li>
        </ul>
      </template>
    </Son>
  </main>
</template>

<script setup lang="ts">
import Son from './Son.vue'
</script>

<style>
.father {
  height: 300px;
  background-color: pink;
  padding: 20px;
}
</style>

兄弟组件传值

可以利用第三方插件 mitt 实现

首先进行安装

yarn add mitt

使用:

  1. src目录下创建一个bus的文件夹,里面创建一个mitt.js的文件。(文件名随便起)

import mitt from 'mitt'

const emitter = mitt()

export default emitter
  1. 发送方:导入mitt.js并用emit发送数据

<template>
  <div class="son">
    <h3>son</h3>
    <button @click="givingGifts">点我,给妹妹送一件礼物</button>
  </div>
</template>

<script setup>
import emitter from '@/bus/mitt'

const givingGifts = () => {
  // 发送方 emitter.emit('事件名', 参数)
  emitter.emit('foo', { car: 'QQ车' })
}
</script>

<style lang="scss" scoped>
.son {
  height: 100px;
  background-color: #91beff;
}
</style>
  1. 接收方:导入mitt.js并用on接收兄弟组件发来的数据

<template>
  <div class="sister">
    <h2>sister</h2>
    <p>接收到了哥哥的礼物:{
   
   { gifts }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import emitter from '@/bus/mitt'

let gifts = ref()

// 接收方  emitter.on('事件名', (参数) => { ... })
emitter.on('foo', (val) => {
  gifts.value = val.car
})
</script>

<style lang="scss" scoped>
.sister {
  height: 100px;
  background-color: #91beff;
}
</style>

父子双向数据绑定

使用 v- model 进行父子组件之间的双向数据绑定

  • v-model不光用在表单上,也可以用在组件上。用在组件上绑定值后,子组件只需要defineProps接收即可

  • v-model用在组件上,默认就是v-model:modelValue,也就是默认绑定modelValue

  • 组件上可以通过v-model传递多个值

父组件

<template>
  <main class="father">
    <h1>father</h1>
    <Son v-model="count" v-model:str="str"></Son>
  </main>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import Son from './son.vue'

let count = ref(100)
let str = ref('str')

</script>

<style>
.father {
  height: 300px;
  background-color: pink;
  padding: 20px;
}
</style>

子组件

<template>
    <div class="son">
        <h2>son</h2>
        <p>接收到的父组件传来的值:{
   
   { modelValue }} -- {
   
   { str }}</p>
    </div>
</template>
  

<script setup lang="ts">
const props = defineProps({
    modelValue: {
        type: Number
    },
    str: String
})
</script>

<style>
.son {
    height: 100px;
    background-color: skyblue;
}
</style>

这只是单纯的父向子传递了数据,如果子组件想要修改,则还需要向父组件发送自定义事件,通知父组件进行修改

实现父子通信

父组件


<template>
  <main class="father">
    <h1>father</h1>
    <Son v-model="count" v-model:str="str" @changeCount="changeCount"></Son>
  </main>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import Son from './son.vue'

let count = ref(100)
let str = ref('str')

const changeCount = (val: number) => {
  count.value = count.value + val
}

</script>

<style>
.father {
  height: 400px;
  background-color: pink;
  padding: 20px;
}
</style>

子组件


<template>
    <div class="son">
        <h2>son</h2>
        <p>接收到的父组件传来的值:{
   
   { modelValue }} -- {
   
   { str }}</p>
        <button @click="changeCount">修改父组件传来的数值</button>
    </div>
</template>
  

<script setup lang="ts">
const props = defineProps({
    modelValue: {
        type: Number
    },
    str: String
})

const $emits = defineEmits(['changeCount'])

const changeCount = () => {
    $emits('changeCount',1)
}
</script>

<style>
.son {
    height: 200px;
    background-color: skyblue;
}
</style>

useAttrs获取父组件传来的属性和事件

  • 可以获取到父组件传递过来的属性和事件

  • 没有被definedProps声明接收的,都会被useAttrs兜底接收

父组件

<template>
  <main class="father">
    <h1>father</h1>
    <Son type="primary" size="small"></Son>
  </main>
</template>

<script setup>
import Son from './Son.vue'
</script>

<style>
.father {
  height: 400px;
  background-color: pink;
  padding: 20px;
}
</style>

子组件

<template>
  <div class="son">
    <h2>son</h2>
    
  </div>
</template>

<script setup>
import { useAttrs } from 'vue'

let $attrs = useAttrs()

console.log($attrs)
</script>

<style>
.son {
  height: 200px;
  background-color: skyblue;
}
</style>

封装第三方的组件时

  • 可以用v-bind绑定$attrs对象。多用于对第三方ui库的组件进行二次开发

<el-button :='$attrs'>
	<slot></slot>    
</el-button>

也可以接收事件

<Son type="primary" size="small" @click="clickSon"></Son>

深层组件传值

深层组件,就是有很多层的嵌套关系。类似于祖先和子孙的关系

使用 provide  和  inject  进行深层组件的传值

  • 祖先组件通过provide这个hook向子孙组件提供数据。它下面的所有子孙组件都可以使用提供的数据

  • 子孙组件通过inject这个hook接收祖先提供的数据

祖先组件

<template>
  <main class="father">
    <h1>father</h1>
    <Son></Son>
  </main>
</template>

<script setup lang="ts">
import { ref,provide } from 'vue'
import Son from './son.vue'

let count = ref(100)

// 提供者:向子孙组件提供数据
provide('count',count)
</script>

<style>
.father {
  height: 300px;
  background-color: pink;
  padding: 20px;
}
</style>

中间组件

<template>
    <div class="son">
        <h2>son</h2>
        <GrandSon></GrandSon>
    </div>
</template>
  
<script setup lang="ts">
import GrandSon from './GrandSon.vue';
</script>

<style>
.son {
    height: 100px;
    background-color: skyblue;
}
</style>

子孙组件

<template>
    <div class="son">
        <h3>grandSon</h3>
        <p>从祖先组件接收过来的数据:{
   
   { count }}</p>
    </div>
</template>
  
<script setup lang="ts">
import { inject } from 'vue'

let count = inject('count')
</script>

<style>
.son {
    height: 100px;
    background-color: skyblue;
}
</style>

Pinia实现任意组件之间通信

  • 核心:stateactionsgetters

选项式用法

定义:

import { defineStore } from 'pinia'

let userInfoStore = defineStore('user', {
  state: () => {
    return {
      name: '张三',
    }
  },
  getters: {},
  actions: {
    changeName(name: String) {
      this.name = name
    },
  },
})

// 对外暴露
export default userInfoStore

使用:

<template>
  <main>
    <p>从数据仓库获取到的数据:{
   
   { userInfo.name }}</p>
    <button @click="changeName">修改数据仓库提供的name的值</button>
  </main>
</template>

<script setup lang="ts">
import userInfoStore from '@/stores/counter'

const userInfo = userInfoStore()

const changeName = () => {
  // 1. 直接进行修改
  // userInfo.name = '李四'
  // 2. 通过 $patch 方法进行调用
  // userInfo.$patch({
  //   name: '李四',
  // })
  // 3. 通过仓库调用自身的方法去修改仓库的数据
  userInfo.changeName('李四')
}
</script>

组合式用法

定义:

import { ref, computed } from 'vue'
import { defineStore } from 'pinia'

const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const doubleCount = computed(() => count.value * 2)
  function increment() {
    count.value++
  }

  return { count, doubleCount, increment }
})

export default useCounterStore

使用:

<template>
  <main>
    <h1>首页</h1>
    <p>
      数据仓库提供的数据:{
   
   { useCounter.count }} -- {
   
   { useCounter.doubleCount }}
    </p>
    <button @click="changeCount">使用数据仓库提供的方法改变count值</button>
  </main>
</template>

<script setup lang="ts">
import useCounterStore from '@/stores/counter'

const useCounter = useCounterStore()

const changeCount = () => {
  useCounter.increment()
}
</script>

猜你喜欢

转载自blog.csdn.net/qq_52845451/article/details/130861829