自学内容网 自学内容网

Vue组件间通信的9种实现方式

组件的通信

父子组件通信

父子组件通信可以理解成:
父组件向子组件传值。
父组件调用子组件的方法。

子组件向父组件传值。
子组件调用父组件的方法。

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();
    }
})

前3种解释了父组件向子组件传值的不同写法,4,5解释了子组件向父组件传值及调用方法。6解释了兄弟组件之间的通信,7解释了不同组件之间通信,8,9状态管理适合用于大型应用,组件众多,状态零散地分布在需要组件和组件之间的交互操作中,复杂度也不断增长的项目。


原文地址:https://blog.csdn.net/friend_ship/article/details/143475614

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!