Vue组件通信方式及最佳实践

发布于:2025-05-23 ⋅ 阅读:(20) ⋅ 点赞:(0)

1. Props / 自定义事件 (父子通信)

使用场景

父子组件直接数据传递

代码实现

<!-- Parent.vue -->
<template>
  <Child :message="parentMsg" @update="handleUpdate" />
</template>

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

const parentMsg = ref('Hello from Parent');
const handleUpdate = (newVal) => {
  parentMsg.value = newVal;
};
</script>

<!-- Child.vue -->
<template>
  <div>
    <p>{{ message }}</p>
    <button @click="sendUpdate">Update Parent</button>
  </div>
</template>

<script setup>
const props = defineProps(['message']);
const emit = defineEmits(['update']);

const sendUpdate = () => {
  emit('update', 'New value from Child');
};
</script>

使用步骤

  1. 父组件通过 :propName 传递数据
  2. 子组件通过 defineProps 接收
  3. 子组件通过 defineEmits 声明事件
  4. 子组件通过 emit('eventName', data) 触发事件
  5. 父组件通过 @eventName 监听处理

关键点

  • 单向数据流原则
  • 子组件不要直接修改 props
  • 适合层级简单场景

2. v-model / .sync (双向绑定)

使用场景

简化父子组件的双向绑定

代码实现

<!-- Parent.vue -->
<template>
  <Child v-model:title="pageTitle" />
  <p>Parent value: {{ pageTitle }}</p>
</template>

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

const pageTitle = ref('Initial Title');
</script>

<!-- Child.vue -->
<template>
  <input 
    :value="title"
    @input="$emit('update:title', $event.target.value)"
  >
</template>

<script setup>
defineProps(['title']);
defineEmits(['update:title']);
</script>

使用步骤

  1. 父组件使用 v-model:propName 绑定
  2. 子组件接收对应 prop
  3. 子组件通过 update:propName 事件更新

关键点

  • Vue 3 支持多个 v-model 绑定
  • 替代 Vue2 的 .sync 修饰符
  • 语法糖,底层仍是 props + events

3. Event Bus (全局事件总线)

使用场景

跨组件通信(小型项目)

代码实现

// eventBus.js
import mitt from 'mitt';
export const emitter = mitt();

// ComponentA.vue (发送方)
import { emitter } from './eventBus';
emitter.emit('global-event', { data: 123 });

// ComponentB.vue (接收方)
import { emitter } from './eventBus';
emitter.on('global-event', (data) => {
  console.log('Received:', data);
});

使用步骤

  1. 创建全局事件总线实例
  2. 发送方使用 emit 触发事件
  3. 接收方使用 on 监听事件
  4. 组件销毁时使用 off 移除监听

关键点

  • 需要手动管理事件监听
  • 适用于简单场景
  • 中大型项目改用状态管理

4. Provide / Inject

使用场景

跨层级组件通信

代码实现

<!-- Ancestor.vue -->
<script setup>
import { provide, ref } from 'vue';

const counter = ref(0);
provide('counter', {
  counter,
  increment: () => counter.value++
});
</script>

<!-- Descendant.vue -->
<script setup>
import { inject } from 'vue';

const { counter, increment } = inject('counter');
</script>

<template>
  <button @click="increment">{{ counter }}</button>
</template>

使用步骤

  1. 祖先组件使用 provide(key, value)
  2. 后代组件使用 inject(key)
  3. 建议提供响应式数据

关键点

  • 适合深层嵌套组件
  • 提供响应式对象更实用
  • 避免组件过度耦合

5. Pinia (状态管理)

使用场景

复杂应用状态管理

代码实现

// stores/counter.js
import { defineStore } from 'pinia';

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

// ComponentA.vue
import { useCounterStore } from './stores/counter';
const store = useCounterStore();
store.increment();

// ComponentB.vue
import { useCounterStore } from './stores/counter';
const store = useCounterStore();
<p>{{ store.count }}</p>

使用步骤

  1. 定义 store
  2. 组件导入并使用 store
  3. 通过 actions 修改状态

关键点

  • 集中式状态管理
  • 支持 TypeScript
  • 替代 Vuex 的现代方案

6. refs 访问组件实例

使用场景

需要直接操作子组件

代码实现

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

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

const childRef = ref(null);

const callChildMethod = () => {
  childRef.value.someMethod();
};
</script>

使用步骤

  1. 使用 ref 属性标记子组件
  2. 通过 ref.value 访问实例
  3. 调用子组件方法/访问属性

关键点

  • 破坏封装性,谨慎使用
  • 优先考虑 props/events
  • 适合集成第三方库

对比总结表

方式 适用场景 优点 缺点
Props/Events 父子组件通信 简单直接 不适合深层嵌套
v-model 双向绑定 语法简洁 只能用于父子组件
Event Bus 跨组件通信 全局可用 难以维护事件流
Provide/Inject 跨层级通信 避免逐层传递 数据来源不透明
Pinia 复杂状态管理 集中管理可维护性强 增加项目复杂度
Refs 直接访问组件 灵活性强 破坏组件封装

通用最佳实践

  1. 简单优先原则:优先使用 Props/Events
  2. 状态共享评估
    • 父子组件 → Props
    • 兄弟组件 → 状态提升到父级
    • 跨层级 → Provide/Inject 或 Pinia
  3. 类型安全:使用 TypeScript 定义 Props 和事件
  4. 响应式处理:对于复杂对象使用 reactive()ref()
  5. 内存管理:及时清理 Event Bus 监听器
  6. 模块化设计:Pinia Store 按功能拆分模块