vue 3 中组件通信的最佳实践

293 阅读3分钟

在 Vue 3 中,组合式 API (setup()) 提供了一种更灵活的方式来组织和重用逻辑。使用组合式 API,我们可以更好地分离关注点,并利用响应式引用和生命周期钩子等功能。下面是使用组合式 API 实现组件间通信的一些最佳实践及其代码示例。

1. Props 和 Emit

这是最基本的通信方式,适用于父子组件之间的通信。

示例

父组件 (Parent.vue)

<template>
  <Child :message="message" @updateMessage="handleUpdateMessage" />
  <button @click="changeMessage">Change Message</button>
</template>

<script setup>
import { ref } from 'vue';
import Child from './Child.vue';

const message = ref('Hello from Parent');

function handleUpdateMessage(newMessage) {
  message.value = newMessage;
}

function changeMessage() {
  message.value = 'Changed by Parent';
}
</script>

子组件 (Child.vue)

<template>
  <div>{{ message }}</div>
  <button @click="updateParent">Update Parent</button>
</template>

<script setup>
import { ref, watch } from 'vue';

const props = defineProps({
  message: String,
});

const emit = defineEmits(['updateMessage']);

function updateParent() {
  emit('updateMessage', 'Hello from Child');
}

watch(() => props.message, (newVal) => {
  console.log('Message changed:', newVal);
});
</script>

2. Refs 和 Custom Events

在 Vue 3 中,你可以使用 refs 来引用子组件,并通过 $refs 访问子组件实例的方法。

示例

父组件 (Parent.vue)

<template>
  <Child ref="childRef" />
  <button @click="callChildMethod">Call Child Method</button>
</template>

<script setup>
import { ref } from 'vue';
import Child from './Child.vue';

const childRef = ref(null);

function callChildMethod() {
  childRef.value.childMethod();
}
</script>

子组件 (Child.vue)

<template>
  <button @click="emitEvent">Emit Event</button>
</template>

<script setup>
import { onMounted } from 'vue';

function emitEvent() {
  console.log('Event emitted');
}

function childMethod() {
  console.log('Called from parent');
}

onMounted(() => {
  console.log('Child component mounted');
});
</script>

3. Composition API 中的 Context

Composition API 提供了 provide/inject 上下文,可以在祖先组件中提供数据,在子孙组件中注入数据。

示例

祖先组件 (Ancestor.vue)

<template>
  <GrandChild />
</template>

<script setup>
import { provide } from 'vue';
import GrandChild from './GrandChild.vue';

provide('message', 'Hello from Ancestor');
</script>

子孙组件 (GrandChild.vue)

<template>
  <div>{{ message }}</div>
</template>

<script setup>
import { inject } from 'vue';

const message = inject('message');
</script>

4. 使用 Vuex

对于更复杂的通信场景,可以使用 Vuex 状态管理库。

示例

store/index.js

import { createStore } from 'vuex';

export default createStore({
  state: {
    count: 0,
  },
  mutations: {
    increment(state) {
      state.count++;
    },
  },
  actions: {
    increment({ commit }) {
      commit('increment');
    },
  },
  getters: {
    doubleCount: (state) => state.count * 2,
  },
});

组件 (Counter.vue)

<template>
  <button @click="increment">Increment</button>
  <div>{{ doubleCount }}</div>
</template>

<script setup>
import { mapActions, mapGetters } from 'vuex';
import { useStore } from 'vuex';

const store = useStore();

function increment() {
  store.dispatch('increment');
}

const doubleCount = store.getters.doubleCount;
</script>

5. 使用 Pinia

Pinia 是 Vue 3 的官方推荐的状态管理库,提供了与 Vuex 类似的功能,但更加简洁易用。

示例

store/index.js

import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
  }),
  actions: {
    increment() {
      this.count++;
    },
  },
  getters: {
    doubleCount: (state) => state.count * 2,
  },
});

组件 (Counter.vue)

<template>
  <button @click="increment">Increment</button>
  <div>{{ doubleCount }}</div>
</template>

<script setup>
import { useCounterStore } from './store/index';

const counterStore = useCounterStore();

function increment() {
  counterStore.increment();
}

const doubleCount = counterStore.doubleCount;
</script>

6. 使用 Event Bus

尽管 Event Bus 不再作为 Vue 3 的官方推荐方法,但它仍然是一种可行的选择,特别是对于简单的跨组件通信。

示例

event-bus.js

import { createApp } from 'vue';

const app = createApp({});
export const eventBus = app.config.globalProperties.$bus;

发布者组件 (Publisher.vue)

<template>
  <button @click="publish">Publish</button>
</template>

<script setup>
import { eventBus } from './event-bus';

function publish() {
  eventBus.emit('my-event', 'Hello from Publisher');
}
</script>

订阅者组件 (Subscriber.vue)

<template>
  <div>{{ message }}</div>
</template>

<script setup>
import { eventBus } from './event-bus';

const message = ref('');

eventBus.on('my-event', (msg) => {
  message.value = msg;
});

onUnmounted(() => {
  eventBus.off('my-event');
});
</script>

7. 使用 MITT方式

MITT 是一个轻量级的事件总线库,特别适合用于 Vue 3 的组合式 API 中的组件间通信。MITT 的设计非常简单,易于集成,并且不引入额外的复杂性。下面是如何使用 MITT 进行组件间通信的详细示例。

安装 MITT

首先,你需要安装 MITT。你可以通过 npm 或 yarn 来安装:

npm install mitt
# 或者
yarn add mitt

创建全局事件总线

在 Vue 3 项目中,你可以在 main.jsmain.ts 文件中创建一个全局的事件总线实例。

main.js

import { createApp } from 'vue';
import App from './App.vue';
import mitt from 'mitt';

const emitter = mitt();

const app = createApp(App);
app.provide('$emitter', emitter);
app.mount('#app');

这里我们创建了一个名为 $emitter 的全局事件总线,并通过 provide() 方法使其在整个应用程序中可用。

使用 MITT 进行通信

示例:父组件向子组件发送事件

父组件 (Parent.vue)

<template>
  <Child />
  <button @click="sendEvent">Send Event</button>
</template>

<script setup>
import { ref } from 'vue';
import Child from './Child.vue';
import { inject } from 'vue';

const $emitter = inject('$emitter');

function sendEvent() {
  $emitter.emit('custom-event', 'Hello from Parent');
}
</script>

子组件 (Child.vue)

<template>
  <div>{{ message }}</div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { inject } from 'vue';

const message = ref('');
const $emitter = inject('$emitter');

onMounted(() => {
  $emitter.on('custom-event', (msg) => {
    message.value = msg;
  });
});

onUnmounted(() => {
  $emitter.off('custom-event');
});
</script>

在这个例子中,父组件通过 $emitter 发送了一个名为 custom-event 的事件,子组件则监听这个事件并在接收到事件时更新其状态。

示例:多个组件之间的通信

组件 A (ComponentA.vue)

<template>
  <button @click="sendEvent">Send Event</button>
</template>

<script setup>
import { onMounted } from 'vue';
import { inject } from 'vue';

const $emitter = inject('$emitter');

function sendEvent() {
  $emitter.emit('global-event', 'Hello from Component A');
}
</script>

组件 B (ComponentB.vue)

<template>
  <div>{{ message }}</div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { inject } from 'vue';

const message = ref('');
const $emitter = inject('$emitter');

onMounted(() => {
  $emitter.on('global-event', (msg) => {
    message.value = msg;
  });
});

onUnmounted(() => {
  $emitter.off('global-event');
});
</script>

在这个例子中,组件 A 发送了一个名为 global-event 的事件,而组件 B 则监听这个事件并在接收到事件时更新其状态。

总结

这些示例展示了如何使用 Vue 3 的组合式 API (setup()) 实现组件间通信的不同方式。选择哪种方法取决于你的具体需求。对于简单的父子组件通信,使用 Props 和 Emit 就足够了。对于更复杂的场景,可以考虑使用 Vuex 或 Pinia。如果你只需要在有限的几个组件之间共享状态,Event Bus 或 Composition API 中的 Context 也是不错的选择。使用MIITT,MITT 是一种轻量级且易于使用的组件间通信方式,非常适合用于 Vue 3 的组合式 API。通过使用全局事件总线,你可以轻松地在组件之间发送和接收事件。这种方式特别适用于需要跨组件进行通信的场景,同时保持代码的简洁性和可维护性。