这位同学来说一说 vue3 的组件通信

文中如有错误欢迎指正!

有人把这个叫做传值但个人感觉通信更合适一点,说到底是两个组件之间的互操作。

可以简单的归类为:父传子、子传父、跨组件

父组件与子组件的通信

父组件向子组件传递数据一般是通过 Props 来实现

props 可以传递任何类型的值 猛击查看官网

通过 Props 传递数据

父组件:引入子组件 Comp1 传入 text、play-state 参数

ts 复制代码
<script setup lang="ts">
import { defineAsyncComponent } from 'vue'

const Comp1 = defineAsyncComponent(() => import('./components/Comp1.vue'))
</script>

<template>
  <Comp1 text="这是一段文本" :play-state="false" />
</template>

<style lang='scss' scoped></style>

子组件:接收父组件传递的 text、play-state 参数

ts 复制代码
<script setup lang="ts">
const { text, playState } = defineProps<{ text: string, playState: boolean }>()
</script>

<template>
  <div>{{ text }}</div>
  <div>{{ playState }}</div>
</template>

<style lang='scss' scoped></style>

通过 Props 让子组件更新传递的 Props 数据(回调函数)

这里需要提一下 props 是单向数据流,也就是你不应该在子组件中直接修改 props 传递过来的数据!

如果你真的想这么做,可以考虑向子组件传递一个更新 props 参数的函数(如果你使用 react 你一定不陌生!)

父组件:传递更改 text 的函数 updateText

ts 复制代码
<script setup lang="ts">
import { defineAsyncComponent, ref } from 'vue'

const Comp1 = defineAsyncComponent(() => import('./components/Comp1.vue'))

const wordText = ref('')

function updateText(text: string) {
  wordText.value = text
}
</script>

<template>
  <Comp1 :text="wordText" :play-state="false" :update-text />
</template>

<style lang='scss' scoped></style>

子组件:接收更改 text 的函数 updateText

ts 复制代码
<script setup lang="ts">
interface CompProps {
  text: string
  playState: boolean
  updateText: (text: string) => void
}

const { text, playState, updateText } = defineProps<CompProps>()
</script>

<template>
  <div>{{ text }}</div>
  <div>{{ playState }}</div>

  <div @click="updateText('1212121')">
    更改text
  </div>
</template>

<style lang='scss' scoped></style>

父组件调用子组件的函数

父组件调用子组件的函数或者获取子组件的变量需要子组件使用 defineExpose 进行导出

子组件导出 getState、curIndex

ts 复制代码
<script setup lang="ts">
import { ref } from 'vue'

interface CompProps {
  text: string
}

const { text } = defineProps<CompProps>()

function getState() {
  console.log('ok')
}

const curIndex = ref(0)

defineExpose({
  getState,
  curIndex,
})
</script>

<template>
  <div>{{ text }}</div>
</template>

<style lang='scss' scoped></style>

父组件调用 getState 函数、获取子组件 curIndex 的值

ts 复制代码
<script setup lang="ts">
import { defineAsyncComponent, onMounted, ref, useTemplateRef } from 'vue'

const Comp1 = defineAsyncComponent(() => import('./components/Comp1.vue'))

const comp1Ref = useTemplateRef('comp1Ref')

onMounted(() => {
  comp1Ref.value?.getState()
  console.log(comp1Ref.value?.curIndex)
})
</script>

<template>
  <Comp1 ref="comp1Ref" :text="wordText" />
</template>

<style lang='scss' scoped></style>

子组件向父组件传递数据

子组件与父组件的通信一般是通过 emit 或者 使用上边介绍的 通过 props 让子组件更新传递的 props 数据 中的方式来实现

这里看下使用 emit 的方式

子组件定义 emit 事件 close、sendText、sendData

ts 复制代码
<script setup lang="ts">
interface CompEmits {
  // 无参数
  (e: 'close'): void
  // 参数是字符串
  (e: 'sendText', payload: string): void
  // 参数是对象
  (e: 'sendData', payload: { value: string }): void
}
const emit = defineEmits<CompEmits>()

// 上边的写法会验证事件携带的参数,如果不需要校验可以写成
// const emit = defineEmits(['close', 'sendText', 'sendData'])
</script>

<template>
  <button @click="emit('sendText', '这是子组件发送的数据 text')">
    发送数据 text
  </button>
  <button @click="emit('sendData', { value: '这是子组件发送的数据 data' })">
    发送数据 data
  </button>
</template>

<style lang='scss' scoped></style>

父组件接收子组件定义的 emit 事件 close、sendText、sendData

ts 复制代码
<script setup lang="ts">
import { defineAsyncComponent } from 'vue'

const Comp1 = defineAsyncComponent(() => import('./components/Comp1.vue'))
function close() {
  console.log('子组件 close 事件')
}

function getData(data: { value: string }) {
  console.log('子组件 sendData 事件', data)
}

function getText(text: string) {
  console.log('子组件 sendText 事件', text)
}
</script>

<template>
  <Comp1 @close="close" @send-data="getData" @send-text="getText" />
</template>

<style lang='scss' scoped></style>

跨组件通信

把不是父子组件通信的都归类到跨组件中:比如兄弟组件、爷孙组件通信。

这里很多人会想到 vuex、pinia 之类的库,它确实可以解决这类问题但它不在本次讨论范围内!

Provide (提供)、Inject (注入)

这是一对 API 解决的是多层级组件 Props 需要逐级透传的问题(拿两张官网的图)

通过 Provide 可以向当前组件的所有后代组件 提供使用 Inject 获取的数据

Provide 可以提供任何类型的数据:比如响应式数据 ref、函数 等

Provide、Inject 只适用于祖先组件与后代组件之间的通信,不适用于任意兄弟组件间通信。

父组件使用 provide 提供数据

ts 复制代码
<script setup lang="ts">
import { defineAsyncComponent, provide, ref } from 'vue'
import { PlayStateKey } from './const'

// 假设 Comp1 是孙子组件
const Comp1 = defineAsyncComponent(() => import('./components/Comp1.vue'))

const playState = ref('success')
provide(PlayStateKey, playState)
</script>

<template>
  <Comp1 />
</template>

<style lang='scss' scoped></style>

推荐使用 Symbol 定义 Provide、Inject 的 key

ts 复制代码
export const PlayStateKey = Symbol('play-sate')

孙子组件使用 inject 获取数据

ts 复制代码
<script setup lang="ts">
import type { Ref } from 'vue'
import { inject } from 'vue'
import { PlaytSateKey } from '../const'

const playState = inject<Ref<string>>(PlayStateKey)
</script>

<template>
  <button>
    {{ playState }}
  </button>
</template>

<style lang='scss' scoped></style>

一种更灵活的方式

pinia 的介绍里有这样一句话

如果你的页面有多层组件嵌套、需要祖先与后代组件通信、兄弟组件通信,那么你可以把这些数据、函数操作提取到一个 ts文件中(如果这些数据需要持久化或许你应该使用 pinia)像下边这样

ts 复制代码
// 有人会对这行代码有疑问吗?
const playState = ref('success')

export function useTestStore() {
  function getTestData() {}

  function formattingData() {}

  function setPlaySate(state: string) {
    playState.value = state
  }

  return {
    playState,
    getTestData,
    formattingData,
    setPlaySate,
  }
}

useTestStore 函数是为了对操作进行分组也可以像下边这样

ts 复制代码
export const playState = ref('success')

export function getTestData() {}

export function formattingData() {}

export function setPlaySate(state: string) {
  playState.value = state
}

我更倾向于第一种

下面看下如何使用:你可以在任何需要获取数据、调用的函数的组件中导入它

父组件

ts 复制代码
<script setup lang="ts">
import { defineAsyncComponent } from 'vue'
import { useTestStore } from './useTestStore'

// 假设 Comp1 是孙子组件
const Comp1 = defineAsyncComponent(() => import('./components/Comp1.vue'))

const { setPlaySate } = useTestStore()

setPlaySate('error')
</script>

<template>
  <Comp1 />
</template>

<style lang='scss' scoped></style>

孙子组件

ts 复制代码
<script setup lang="ts">
import { useTestStore } from '../useTestStore'

const { playState, getTestData, formattingData } = useTestStore()
getTestData()
formattingData()
</script>

<template>
  <button>
    {{ playState }}
  </button>
</template>

<style lang='scss' scoped></style>

跨组件事件调用

这个可以看做是任意两个组件的互操作:比如兄弟组件A 想要触发 兄弟组件B的子组件的事件,这个时候不要犹豫直接上 mitt、vueuse useEventBus 来实现!

查看篇文章来学习 mitt、vueuse useEventBus 的使用以及是如何实现的!

深入解析 mitt 和 vueuse useEventBus 的实现

相关推荐
Moment30 分钟前
从方案到原理,带你从零到一实现一个 前端白屏 检测的 SDK ☺️☺️☺️
前端·javascript·面试
鱼樱前端35 分钟前
Vue3 + TypeScript 整合 MeScroll.js 组件
前端·vue.js
拉不动的猪1 小时前
刷刷题29
前端·vue.js·面试
野生的程序媛1 小时前
重生之我在学Vue--第5天 Vue 3 路由管理(Vue Router)
前端·javascript·vue.js
codingandsleeping1 小时前
前端工程化之模块化
前端·javascript
CodeCraft Studio1 小时前
报表控件stimulsoft操作:使用 Angular 应用程序的报告查看器组件
前端·javascript·angular.js
阿丽塔~2 小时前
面试题之vue和react的异同
前端·vue.js·react.js·面试
烛阴3 小时前
JavaScript 性能提升秘籍:WeakMap 和 WeakSet 你用对了吗?
前端·javascript
yuren_xia3 小时前
eclipse创建maven web项目
前端·eclipse·maven
鱼樱前端3 小时前
Vue 2 与 Vue 3 语法区别完整对比
前端·javascript·vue.js