Communication between Vue3 parent-child components
foreword
This article mainly records the four ways Vue3 passes parameters between parent and child components under the syntax sugar of setup
Vue3+TypeScript
1. Father-to-son defineProps
The parent component passes the value to the child component mainly by the parent component binding the value for the child component through v-bind, and then passing it to the child component; the child component receives and uses it through defineProps.
The following is the parent componentFather.vue
<template>
<div class="fa">
<div style="margin: 10px;">我是父组件</div>
<Son :fatherMessage="fatherMessage"></Son>
</div>
</template>
<script setup lang="ts">
import Son from './Son.vue'
import {
ref} from "vue";
const fatherMessage = ref<string>("我是父组件传过来的值")
</script>
<style scoped>
.fa{
border: 3px solid cornflowerblue;
width: 400px;
text-align: center;
}
</style>
The following is the subcomponent Son.vue
<template>
<div style="margin: 10px;border: 2px solid red">
我是子组件
<div style="margin: 5px;border: 2px solid gold">
父组件传值接收区:{
{
fatherMessage}}
</div>
</div>
</template>
<script setup lang="ts">
interface Props {
fatherMessage?: string,
}
defineProps<Props>()
</script>
Father.vue
When calling this child component in the parent component Son.vue
, use v-bind
the binding parameter fatherMessage
and pass it toSon.vue
Subcomponents Son.vue
use defineProps
to receive fatherMessage
this parameter, and then they can use this parameter normally.
Two, Child and Father defineEmits
The child component passes the value to the parent component mainly by defineEmits
registering a custom event, and then triggering emit
to call the custom event, and passing parameters to the parent component.
When calling the child component in the parent component, by v-on
binding a function, the passed value is obtained through the function.
The following are subcomponentsSon.vue
<template>
<div style="margin: 10px;border: 2px solid red">
我是子组件
<button @click="transValue" style="margin: 5px">传值给父组件</button>
</div>
</template>
<script setup lang="ts">
import {
ref} from "vue";
// 定义所要传给父组件的值
const value = ref<String>("我是子组件传给父组件的值")
// 使用defineEmits注册一个自定义事件
const emit = defineEmits(["getValue"])
// 点击事件触发emit,去调用我们注册的自定义事件getValue,并传递value参数至父组件
const transValue = () => {
emit("getValue", value.value)
}
</script>
The following is the parent componentFather.vue
<template>
<div class="fa">
<div style="margin: 10px;">我是父组件</div>
父组件接收子组件传的值:{
{
sonMessage}}
<Son @getValue="getSonValue"></Son>
</div>
</template>
<script setup lang="ts">
import Son from './Son.vue'
import {
ref} from "vue";
const sonMessage = ref<string>("")
const getSonValue = (value: string) => {
sonMessage.value = value
}
</script>
<style scoped>
.fa{
border: 3px solid cornflowerblue;
width: 400px;
text-align: center;
}
</style>
Father.vue
When calling this child component in the parent component Son.vue
, when the child component Son.vue
needs to pass parameters to the parent component Father.vue
, defineEmits
register an event getValue
, then set the click event transValue
to trigger emit
, call the custom event we registered getValue
, and pass value
the parameters to the parent component.
When the parent component obtains the value passed by Father.vue
the child component , it uses the setting response function on the child component (the function must be consistent with the name of the registered custom event in the child component ) , and binds a function to obtain the passed value.Son.vue
v-on
getValue
getValue
getSonValue
3. The child component exposes properties to the parent component defineExpose
When the parent component wants to directly call the properties or methods of the parent component, the child component can use defineExpose
the property or method that exposes itself, and the parent component uses ref
the property or method exposed by calling the child component.
The following are subcomponentsSon.vue
<template>
<div style="margin: 10px;border: 2px solid red">
我是子组件
</div>
</template>
<script setup lang="ts">
import {
ref, defineExpose} from "vue";
// 暴露给父组件的值
const toFatherValue = ref<string>("我是要暴露给父组件的值")
// 暴露给父组件的方法
const toFatherMethod = () => {
console.log("我是要暴露给父组件的方法")
}
// 暴露方法和属性给父组件
defineExpose({
toFatherMethod, toFatherValue})
</script>
The following is the parent componentFather.vue
<template>
<div class="fa">
<div style="margin: 10px;">我是父组件</div>
<button @click="getSonMethod">获取子组件的方法</button>
<Son ref="sonMethodRef"></Son>
</div>
</template>
<script setup lang="ts">
import Son from './Son.vue'
import {
ref} from "vue";
const sonMethodRef = ref()
const getSonMethod = () => {
sonMethodRef.value.toFatherMethod()
console.log(sonMethodRef.value.toFatherValue)
}
</script>
<style scoped>
.fa{
border: 3px solid cornflowerblue;
width: 400px;
text-align: center;
}
</style>
toFatherValue
Define properties and methods in subcomponents toFatherMethod
, and then defineExpose
expose them through.
When the parent component calls, bind one for the child component ref
and define a ref
variable to get the properties and methods exposed by the child component sonMethodRef
by calling .sonMethodRef
4. Dependency injection Provide / Inject
From the above introduction, we can understand the communication between parent and child components, but there is such a situation: there are some multi-level nested components, forming a huge component tree, and a deep subcomponent needs a Partial data in a distant ancestor component. In this case, if you only use props, you have to pass them down the component chain, which is very cumbersome:
Although Footer
the components may not care about these at all props
, they still need to be defined and passed down in order to DeepChild
enable access to them. If the component chain is very long, it may affect more components on this road. This problem is called "prop step-by-step transparent transmission" , and it is obviously a situation we want to avoid as much as possible.
provide
and inject
can help us with this. A parent component acts as a dependency provider for all its descendants. Any descendant of the component tree, no matter how deep the hierarchy is, can inject the dependencies provided by the parent component for the entire chain.
The following is the parent componentRoot.vue
<template>
<div>
我是root组件
<Footer></Footer>
</div>
</template>
<script setup lang="ts">
import {
provide, ref } from 'vue'
import Footer from './Footer.vue'
const toChildValue= ref<string>("我是给所有子组件的值")
// 将toChildValue注入到所有子组件中
provide(/* 注入名 */ 'toChildValue', /* 值 */ toChildValue)
</script>
The following are subcomponentsFooter.vue
<template>
<div>
我是footer组件
<div>
接收父组件的值:{
{
getFatherValue}}
</div>
<DeepChild></DeepChild>
</div>
</template>
<script setup lang="ts">
import DeepChild from "./DeepChild.vue"
import {
ref,inject,Ref} from "vue";
// 获取父组件提供的值
// 如果没有祖先组件提供 "toChildValue"
// ref("") 会是 "这是默认值"
const getFatherValue = inject<Ref<string>>(/* 注入名 */"toChildValue",/* 默认值 */ ref(""))
</script>
The following is the grandchildren componentDeepChild.vue
<template>
<div>
我是deepChild组件
<div>
接收爷爷组件的值:{
{getGrandFatherValue}}
</div>
</div>
</template>
<script setup lang="ts">
import {
inject, ref, Ref} from "vue";
// 获取爷爷组件提供的值
// 如果没有爷爷组件提供 "toChildValue"
// value 会是 ""
const getGrandFatherValue = inject<Ref<string>>(/* 注入名 */"toChildValue",/* 默认值 */ ref(""))
</script>
When the topmost component Root.vue
passes values to all child components, provide
inject using
provide(/* 注入名 */ 'toChildValue', /* 值 */ toChildValue)
Then no matter which subcomponent wants to get toChildValue
the value, just use inject
it
inject<Ref<string>>(/* 注入名 */"toChildValue",/* 默认值 */ ref(""))
When providing/injecting reactive data, if you want to change the data, it is recommended to keep any changes to the reactive state as much as possible in the supplier component , the root component Root.vue
. This ensures that both the declaration of the provided state and the mutating operations are cohesive within the same component, making it easier to maintain.
Sometimes, we may need to change data in the injector component. In this case, we recommend declaring and providing a method function to change data in the supplier component:
the parent component is as followsRoot.vue
<template>
<div>
我是root组件
<Footer></Footer>
</div>
</template>
<script setup lang="ts">
import {
InjectionKey, provide, Ref, ref} from 'vue'
import Footer from './Footer.vue'
const toChildValue= ref<string>("我是给所有子组件的值")
/**
* 修改父组件值的方法
*/
const changeValue = () => {
toChildValue.value = "我是父组件修改的值"
}
// 定义一个注入key的类型(建议将注入 key 的类型放在一个单独的文件中,这样它就可以被多个组件导入)
interface ProvideType {
toChildValue: Ref<string>;
changeValue: () => void;
}
// 为注入值标记类型
const toValue = Symbol() as InjectionKey<ProvideType>
// 将toChildValue和changeValue注入到所有子组件中
provide(/* 注入名 */ 'toValue', /* 值 */{
toChildValue,
changeValue
})
</script>
provide
and will inject
usually run in different components. To correctly mark the type of the injected value, Vue provides an InjectionKey
interface , which is a Symbol
generic type inherited from that can be used to synchronize the type of the injected value between the provider and the consumer.
It is recommended to put key
injected types in a separate file so that it can be imported by multiple components.
// 定义一个注入key的类型
//(建议将注入 key 的类型放在一个单独的文件中,这样它就可以被多个组件导入)
interface ProvideType {
toChildValue: Ref<string>;
changeValue: () => void;
}
// 为注入值标记类型
const toValue = Symbol() as InjectionKey<ProvideType>
The following is the grandchildren componentDeepChild.vue
<template>
<div>
我是deepChild组件
<div>
<button @click="changeValue">改变祖先组件的值</button>
{
{
toChildValue}}
</div>
</div>
</template>
<script setup lang="ts">
import {
inject, ref, Ref} from "vue";
// 定义注入值的类型
interface ProvideType {
toChildValue: Ref<string>;
changeValue: () => void;
}
// 解构获取父组件传的值,需要进行强制类型转换
const {
toChildValue, changeValue} = inject(/* 注入名 */"toValue") as ProvideType
// 不解构时,只需指定类型即可
// const value = inject<ProvideType>(/* 注入名 */"toValue")
</script>
When the ancestor component provides parameters and methods, the child component needs to cast the type of the value when deconstructing
// 解构获取父组件传的值
const {
toChildValue, changeValue} = inject(/* 注入名 */"toValue") as ProvideType
If the subcomponent does not destructure when used, just specify the type directly
// 不解构时,直接指定类型即可
const value = inject<ProvideType>(/* 注入名 */"toValue")
reference
1. Xiaoman ZS Learning Vue3 Chapter 23 (Dependency Injection Provide / Inject) https://blog.csdn.net/qq1195566313/article/details/123143981?spm=1001.2014.3001.5501
2. Vue3 official website dependency injection
https:/ /cn.vuejs.org/guide/components/provide-inject.html