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