文中如有错误欢迎指正!
有人把这个叫做传值但个人感觉通信更合适一点,说到底是两个组件之间的互操作。
可以简单的归类为:父传子、子传父、跨组件
父组件与子组件的通信
父组件向子组件传递数据一般是通过 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 的使用以及是如何实现的!