组件的通信
父子组件通信
父子组件通信可以理解成:
父组件向子组件传值。
父组件调用子组件的方法。
子组件向父组件传值。
子组件调用父组件的方法。
1.props: 利用props属性实现父组件向子组件传值。
parent.vue文件
<template><div>父组件<!-- 子组件在不同的父组件中传递 --><children data="我是父组件传递的值" ></children><children data="我是父组件传递的值" :num="18"></children></div>
</template>
<script setup lang="ts">
import children from '@/components/children.vue'
</script>
children.vue 文件
<template><div>子组件:{{data}}<div>年龄: {{num}}</div></div>
</template>
<script lang="ts">
import { defineComponent } from "vue";export default defineComponent({name: 'children',props: {data: String,num: {type: Number,default: () => 20},}
})
</script>
props不仅可以传字符串、数字、数组、对象、boolean的值,在传递时props也可以定义数据校验,以此来限制接收的数据类型等。
父传子的方式形成了一个单向下行绑定,叫做单向数据流。父级props的更新也会向下流程到接收这个props的子组件中,触发对应的更新,但是反过来则不行。如果遇到确实需要改变props值的应用场合,则采用下面的解决方法:
children.vue文件
<template><div><div>必须被修改的值第一种: {{myData}}</div><div>必须被修改的值第二种: {{childData}}</div></div>
</template>
<script lang="ts">
import { computed, defineComponent, ref } from "vue";export default defineComponent({name: 'children',props: {data: String,num: {type: Number,default: () => 20},},setup(props) {// 第一种const myData = ref(props.data)myData.value = '通过初始化的方式进行修改'// 第二种const childData = computed(() => {return props.data + '通过computed属性实现修改'})return {myData,childData}}
})
</script>
2. $refs: 利用$refs属性可以实现父组件调用子组件的方法。通常用于直接操作子组件,但不推荐用于数据传递。
Vue2的写法
<refChild ref="childRef"></refChild>this.$refs.refChild?.childFn()
Vue3的写法
parent.vue文件
<template><div class="mt15"> $refs通信的子组件<refChild ref="compontentRef"></refChild></div>
</template>
<script lang="ts">
import refChild from '@/components/refChild.vue'
import { defineComponent, onMounted, ref } from 'vue'export default defineComponent({name: 'parent',components: {refChild},setup() {const compontentRef = ref(null)const getRefChild = () => {compontentRef.value?.childFn();}onMounted(() => {getRefChild();})return {compontentRef,getRefChild}}
})
refChild.vue文件
<template><div>$refs紫组件</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";export default defineComponent({name: 'refChild',setup(props, context) {const childFn = () => {console.log('fn的方法被调用')}// 第一种写法context.expose({ childFn: childFn })// 第二种写法 defineExpose不需要引入,直接使用// defineExpose({ childFn: childFn })}
})
</script>
3.slot: 通过插槽实现父组件向子组件传值。关于插槽
// 父组件<div><slotChild>默认传值的方式</slotChild></div>
// 子组件
<template><div><slot></slot></div>
</template>
4.$emit: 触发当前组件实例上的事件,可以理解成子组件触发了绑定在父组件上的自定义事件。
parent.vue文件
<children data="我是父组件传递的值" :num="18" @fromGreet="fromGreet"></children>
import { defineComponent, onMounted, ref } from 'vue'
import children from '@/components/children.vue'
import refChild from '@/components/refChild.vue'
import slotChild from '@/components/slotChild.vue'export default defineComponent({name: 'parent',components: {children,},setup() {const fromGreet = (data) => {console.log('来自子组件的调用', data)}return {fromGreet}}
})
children.vue文件
<template><div>子组件:{{data}} <a-button type="primary" @click="greet">打招呼</a-button></div>
</template>
import { computed, defineComponent, ref } from "vue";export default defineComponent({name: 'children',props: {data: String,},setup(props, context) {const greet = () => {context.emit('fromGreet', 'hello')}return {greet}}
})
5.$parent: 在子组件直接获取父组件的实例,从而调用父组件中的方法,类似于$refs获取子组件的方法。
因Vue3的组合式API设计原则,应该尽量避免直接使用$parent,可能会导致组件之间的耦合度过高。推荐下面的实现方法:
parent.vue文件
<children @call-parent-method="toChildClick"></children>
const toChildClick = (data) => {console.log('来自子组件的调用', data)}
children.vue文件
<a-button danger @click="getParentFn">调用父组件的方法</a-button>
import { computed, defineComponent, ref } from "vue";export default defineComponent({name: 'children',emits: ['call-parent-method'],setup(props, context) {const getParentFn = () => {context.emit('call-parent-method');}return {getParentFn}}
})
Vue2的写法可参考$refs。
6. 事件总线EventBus/mitt实现兄弟组件通信
在Vue2中,可以采用EventBus这种方法。利用一个空的Vue实例来作为桥梁,实现事件分发,它的工作原理是发布/订阅方法,通常称为Pub/Sub,也就是发布和订阅的模式。
var bus = new Vue()// 组件A
bus.$emit('event', 'hello from component A')// 组件B
bus.$on('event', (message) => {console.log(message); // 'hello from component A'
})
在Vue3中,采用第三方事件总线库mitt取代了EventBus事件。mitt的使用方法和EventBus非常类似,同样是基于Pub/Sub模式,并且更加简单,可以在需要进行通信的地方直接使用。
首先需要安装:
npm install mitt
创建一个工具包 eventBus.ts
import mitt from 'mitt';
export const emitter = mitt();
在componentA组件
<a-button type="primary" @click="sendMessage">发送消息给兄弟组件</a-button>
import { emitter } from '@/utils/eventBus'
export default defineComponent({setup(props, context) {const sendMessage = () => {// 发布一个事件emitter.emit('sendMessage', 'hello from component A')}return {sendMessage}}
})
在componetB组件
<div>message from component A: {{ message }}
</div>
import { defineComponent, onMounted, onUnmounted, ref } from "vue";
import { emitter } from '@/utils/eventBus'export default defineComponent({setup(props, context) {const message = ref('')onMounted(() => {emitter.on('sendMessage', (msg) => {message.value = msg;})})onUnmounted(() => {emitter.off('sendMessage')})return {message}}
})
事件总线的方式进行通信使用起来非常简单,可以实现任意组件之间的通信,其中没有多余的业务逻辑,只需要在状态变化组件触发一个事件,随后在处理逻辑组件监听该事件即可。
7. provide/inject AP实现跨级组件
provide/inject是一种跨组件传递数据的方式,允许祖先组件向其所有子孙后代注入依赖,而不必一层层地通过props传递。
import { defineComponent, provide, ref } from 'vue'
export default defineComponent({setup(props, context) {const theme = ref('dark')provide('theme', theme)}
})
<div>主题: {{ theme }}
</div>
import { defineComponent, ref, inject } from "vue";export default defineComponent({setup(props, context) {const theme = inject('theme')return {theme}}
})
8.vuex状态管理
对于一些大型的项目,要实现复杂的组件通信和状态管理。
组合式API的写法
import { createStore } from 'vuex'export default createStore({state: {message: 'vue'},mutations: {UPDATE_MESSAGE(state, newMessage) {state.message = newMessage},},actions: {updateMessage({ commit }, message) {commit('UPDATE_MESSAGE', message)},},getters: {message: state => state.message;}
})// 在组件中获取信息
const store = useStore()const message = computed(() => store.getters.message)const updateMessage = () => {store.dispatch('updateMessage','New Message')
}
9.pinia状态管理
Pinia主打简单和轻量,其大小仅有1KB。
组合式API写法
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'export const useCounterStore = defineStore('counter', () => {const count = ref(0)const doubleCount = computed(() => count.value * 2)const increment = () => {count.value++}return { count, doubleCount, increment }
})// 在文件中引入
import { useCounterStore } from '@/stores/counter'export default defineComponent({setup() {const counter = useCounterStore()counter.count++;// 两种写法// 第一种counter.$patch({ count: counter.count + 1})// 第二种counter.increment();}
})