Vue3父子组件间传参通信
Posted 不会写代码的小周
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue3父子组件间传参通信相关的知识,希望对你有一定的参考价值。
Vue3 父子组件间通信
前言
本文主要是记录Vue3在setup语法糖下的父子组件间传参的四种方式
Vue3+TypeScript
一、父传子 defineProps
父组件传值给子组件主要是由父组件为子组件通过v-bind绑定数值,而后传给子组件;子组件则通过defineProps接收使用。
如下为父组件Father.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>
如下为子组件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
中在调用Son.vue
这个子组件时,使用v-bind
绑定参数fatherMessage
,并传给Son.vue
子组件Son.vue
使用defineProps
接收fatherMessage
这个参数,而后就可以正常使用该参数。
二、子传父 defineEmits
子组件传值给父组件主要是子组件通过defineEmits
注册一个自定义事件,而后触发emit
去调用该自定义事件,并传递参数给父组件。
在父组件中调用子组件时,通过v-on
绑定一个函数,通过该函数获取传过来的值。
如下为子组件Son.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>
如下为父组件Father.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
中在调用Son.vue
这个子组件时,当子组件Son.vue
需要传参给父组件Father.vue
时,使用defineEmits
注册一个事件getValue
,而后设置点击事件transValue
去触发emit
,去调用我们注册的自定义事件getValue
,并传递value
参数至父组件。
父组件Father.vue
在获取子组件Son.vue
传过来的值时,通过在子组件上使用v-on
设置响应函数getValue
(该函数与子组件中的注册自定义事件getValue
名称需一致),并绑定一个函数getSonValue
来获取传过来的值。
三、子组件暴露属性给父组件 defineExpose
当父组件想直接调用父组件的属性或者方法时,子组件可以使用defineExpose
暴露自身的属性或者方法,父组件中使用ref
调用子组件暴露的属性或方法。
如下为子组件Son.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>
如下为父组件Father.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
和方法toFatherMethod
,而后通过defineExpose
暴露出来。
父组件调用时,为子组件绑定一个ref
,并定义一个ref
变量sonMethodRef
,通过调用sonMethodRef
,来获取子组件暴露出来的属性和方法。
四、依赖注入Provide / Inject
从上面的介绍里我们可以了解到父子组件之间的通信,但是却存在这样的情况:有一些多层级嵌套的组件,形成了一颗巨大的组件树,而某个深层的子组件需要一个较远的祖先组件中的部分数据。在这种情况下,如果仅使用 props 则必须将其沿着组件链逐级传递下去,这会非常麻烦:
虽然这里的 Footer
组件可能根本不关心这些 props
,但为了使 DeepChild
能访问到它们,仍然需要定义并向下传递。如果组件链路非常长,可能会影响到更多这条路上的组件。这一问题被称为“prop 逐级透传”,显然是我们希望尽量避免的情况。
provide
和 inject
可以帮助我们解决这一问题。 一个父组件相对于其所有的后代组件,会作为依赖提供者。任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖。
如下为父组件Root.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>
如下为子组件Footer.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>
如下为孙子组件DeepChild.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>
当最顶层的组件Root.vue
传值给所有子组件时,使用provide
进行注入
provide(/* 注入名 */ 'toChildValue', /* 值 */ toChildValue)
而后无论哪个子组件想要获取toChildValue
的值,只需使用inject
即可
inject<Ref<string>>(/* 注入名 */"toChildValue",/* 默认值 */ ref(""))
当提供 / 注入响应式的数据时,如果想改变数据时,建议尽可能将任何对响应式状态的变更都保持在供给方组件中,即根组件Root.vue
。这样可以确保所提供状态的声明和变更操作都内聚在同一个组件内,使其更容易维护。
有的时候,我们可能需要在注入方组件中更改数据。在这种情况下,我们推荐在供给方组件内声明并提供一个更改数据的方法函数:
如下为父组件Root.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
和 inject
通常会在不同的组件中运行。要正确地为注入的值标记类型,Vue 提供了一个 InjectionKey
接口,它是一个继承自 Symbol
的泛型类型,可以用来在提供者和消费者之间同步注入值的类型。
建议将注入 key
的类型放在一个单独的文件中,这样它就可以被多个组件导入。
// 定义一个注入key的类型
//(建议将注入 key 的类型放在一个单独的文件中,这样它就可以被多个组件导入)
interface ProvideType
toChildValue: Ref<string>;
changeValue: () => void;
// 为注入值标记类型
const toValue = Symbol() as InjectionKey<ProvideType>
如下为孙子组件DeepChild.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>
当祖先组件提供参数与方法时,子组件在解构时需要强制转换该值的类型
// 解构获取父组件传的值
const toChildValue, changeValue = inject(/* 注入名 */"toValue") as ProvideType
如果子组件在使用时不进行解构,则直接指明类型即可
// 不解构时,直接指定类型即可
const value = inject<ProvideType>(/* 注入名 */"toValue")
参考
1、小满ZS 学习Vue3 第二十三章(依赖注入Provide / Inject) https://blog.csdn.net/qq1195566313/article/details/123143981?spm=1001.2014.3001.5501
2、Vue3官网 依赖注入
https://cn.vuejs.org/guide/components/provide-inject.html
vue非父子组件间传参问题
最近在使用vue进行开发,遇到了组件之间传参的问题,此处主要是针对非父子组件之间的传参问题进行总结,方法如下:
一、如果两个组件用友共同的父组件,即
FatherComponent.vue代码
<template>
<child-component1/>
<child-component2/>
</template>
此时需要组件1给组件2传递某些参数,实现如下:
1、父组件给组件1绑定一个方法,让组件1进行回调,组件2接收某个属性,通过改变父组件的数据从而实现组件2的属性值的更新,即
父组件
<child-component1 :callback="child1Callback" />
<child-component2 :props="child2Props" />
data () {
return {
child2Props: '';
}
}
child1Callback ([args...]) {
// 此处更新了父组件的data,使组件2的属性prop改变
this.child2Props = [args...]
}
组件1
props: ['callback']
change () {
this.callback([args...])
}
2、通过bus进行实现,首先bus.js如下
const bus = new Vue()
export default bus
组件1
import bus from './bus'
methods: {
change () {
bus.$emit('callComponent2', [args...])
}
}
组件2
import bus from './bus'
mounted () {
bus.$on('callComponent2', function ([args...]) {
// 做你想做的
})
}
3、利用vuex实现,创建store.js相当于某些全局变量,但是可以被vue检测到变化
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
pageNo: 0
},
mutations: {
'change' (state, [args...]) {
state.pageNo++
}
}
})
export default store
项目入口js
import store from './store.js'
new Vue({
...
store: store
...
})
此时在任意vue文件都可以改变pageNo的值,并且会影响到所有使用了pageNo的组件,都会进行更新
childComponent1.vue
this.$store.commit('change')
childComponent2.vue
{{this.$store.state.pageNo}}
总结:
1、第一种比较绕,需要有共同的父组件,如果组件间层级太多,会导致代码混乱,比较难维护。
2、第二种比较直观,推荐使用,但是要理解,请仔细阅读官方文档
3、利用vuex在很多项目中都会显得杀鸡用牛刀,虽然更好理解,但是也带来了学习成本,并且可能会有一些副作用,但是如果项目比较复杂,利用vuex更加直观
综合各种方法的优缺点,推荐使用第二种,项目过于复杂请使用第三种
如果有更好的方法,请留言指教,谢谢
原文地址:https://segmentfault.com/a/1190000012555128
以上是关于Vue3父子组件间传参通信的主要内容,如果未能解决你的问题,请参考以下文章