Vue3的组件通信方式

通信方式 适用层级 数据流向 复杂度
Props/Emits 父子组件 单向/双向 ★☆☆
v-model 父子组件 双向 ★☆☆
Provide/Inject 跨层级组件 自上而下 ★★☆
事件总线 任意组件 任意方向 ★★★
Pinia/Vuex 全局状态 任意方向 ★★☆
Refs模板引用 父子组件 父→子 ★☆☆
作用域插槽 父子组件 子→父 ★★☆
Web Workers 跨线程通信 任意方向 ★★★★

1. Props/Emits:基础父子通信

适用场景 :直接父子组件通信
注意:避免直接修改props,使用emit通知父组件修改

javascript 复制代码
<!-- 父组件 -->
<script setup>
import Child from './Child.vue';
const message = ref('父组件数据');
const handleEmit = (data) => {
  console.log('子组件传递:', data);
};
</script>

<template>
  <Child :msg="message" @child-event="handleEmit" />
</template>

<!-- 子组件 Child.vue -->
<script setup>
const props = defineProps(['msg']);
const emit = defineEmits(['child-event']);

const sendToParent = () => {
  emit('child-event', { time: new Date() });
};
</script>

<template>
  <div>收到: {{ msg }}</div>
  <button @click="sendToParent">发送事件</button>
</template>
2. v-model 双向绑定升级

优势 :替代Vue2的.sync修饰符,语法更简洁
原理 :相当于 :modelValue + @update:modelValue

javascript 复制代码
<!-- 父组件 -->
<script setup>
import CustomInput from './CustomInput.vue';
const username = ref('');
</script>

<template>
  <CustomInput v-model="username" />
</template>

<!-- 子组件 CustomInput.vue -->
<script setup>
const model = defineModel();
</script>

<template>
  <input 
    type="text"
    :value="model"
    @input="model = $event.target.value"
  />
</template>
3. Provide/Inject 跨层级通信

适用场景 :多级嵌套组件共享数据
注意:避免滥用,复杂场景建议用状态管理

javascript 复制代码
// 祖先组件
<script setup>
import { provide, ref } from 'vue';
const theme = ref('dark');
provide('app-theme', {
  theme,
  toggle: () => {
    theme.value = theme.value === 'dark' ? 'light' : 'dark';
  }
});
</script>

// 任意后代组件
<script setup>
import { inject } from 'vue';
const { theme, toggle } = inject('app-theme');
</script>

<template>
  <button @click="toggle">
    当前主题: {{ theme }}
  </button>
</template>
4. 事件总线替代方案(mitt)

适用场景 :非父子组件通信
优势 :轻量级(仅200B),替代Vue2的$emit/$on

javascript 复制代码
// eventBus.js
import mitt from 'mitt';
export default mitt();

// 组件A(发布事件)
import bus from './eventBus';
bus.emit('user-login', { user: 'admin' });

// 组件B(订阅事件)
import bus from './eventBus';
bus.on('user-login', (userData) => {
  console.log('用户登录:', userData);
});

// 组件卸载时取消订阅
onUnmounted(() => {
  bus.off('user-login');
});
5. pinia状态管理(推荐)

优势 :类型安全、Devtools支持、模块化设计
对比Vuex:更简洁API,去除mutations概念

javascript 复制代码
// stores/user.js
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
  state: () => ({ name: '', isLogin: false }),
  actions: {
    login(name) {
      this.name = name;
      this.isLogin = true;
    }
  }
});

// 组件中使用
<script setup>
import { useUserStore } from '@/stores/user';
const userStore = useUserStore();

const login = () => {
  userStore.login('张三');
};
</script>

<template>
  <div>用户名: {{ userStore.name }}</div>
</template>
6. 模板引用通信

适用场景 :父组件需要直接访问子组件方法或数据
限制:只能在父子组件间使用

javascript 复制代码
<!-- 父组件 -->
<script setup>
import Child from './Child.vue';
import { ref, onMounted } from 'vue';

const childRef = ref(null);

onMounted(() => {
  // 调用子组件方法
  childRef.value?.childMethod();
  
  // 访问子组件数据
  console.log(childRef.value?.childData);
});
</script>

<template>
  <Child ref="childRef" />
</template>

<!-- 子组件 Child.vue -->
<script setup>
import { defineExpose } from 'vue';

const childData = ref('子组件数据');
const childMethod = () => {
  console.log('子组件方法被调用');
};

// 暴露给父组件
defineExpose({
  childData,
  childMethod
});
</script>
7. 作用域插槽(子→父通信)

适用场景 :子组件需要向父组件传递渲染内容
优势:保持子组件封装性的同时提供定制能力

javascript 复制代码
<!-- 子组件 ScopedList.vue -->
<script setup>
const items = ref(['Vue', 'React', 'Angular']);
</script>

<template>
  <ul>
    <li v-for="(item, index) in items" :key="index">
      <slot :item="item" :index="index" />
    </li>
  </ul>
</template>

<!-- 父组件 -->
<script setup>
import ScopedList from './ScopedList.vue';
</script>

<template>
  <ScopedList v-slot="{ item, index }">
    <span :class="{ active: index === 0 }">{{ item }}</span>
  </ScopedList>
</template>
8. Web Workers 跨线程通信

适用场景 :CPU密集型任务,避免阻塞UI线程
注意:worker中无法访问DOM

javascript 复制代码
// worker.js
self.onmessage = (e) => {
  const result = heavyCalculation(e.data);
  self.postMessage(result);
};

function heavyCalculation(data) {
  // 复杂计算逻辑
  return data * 2;
}

// 组件中使用
<script setup>
import { ref } from 'vue';

const worker = new Worker('./worker.js');
const result = ref(0);

worker.onmessage = (e) => {
  result.value = e.data;
};

const startCalc = () => {
  worker.postMessage(1000000); // 发送大数据
};
</script>

<template>
  <button @click="startCalc">开始计算</button>
  <div>结果: {{ result }}</div>
</template>

通信方式对比指南

通信方式 适用场景 优点 缺点
Props/Emits 父子组件简单通信 简单直接 多层传递繁琐
v-model 表单组件双向绑定 语法简洁 仅适用特定场景
Provide/Inject 跨层级组件共享 避免逐层传递 数据来源不透明
事件总线 任意组件间事件通知 灵活解耦 难以跟踪调试
Pinia/Vuex 全局状态管理 集中管理,调试友好 学习成本较高
模板引用 父组件访问子组件内部 精确访问 破坏组件封装性
作用域插槽 子组件向父组件暴露渲染数据 灵活定制UI 仅适用于模板内容
Web Workers 后台计算任务 避免UI阻塞 通信成本高

实战选型建议

  1. 父子组件 :优先使用 props/emits + v-model

  2. 兄弟组件 :采用共享父组件状态 或 事件总线

  3. 祖孙组件 :使用 provide/inject

  4. 复杂应用Pinia 管理全局状态

  5. 性能敏感Web Workers 处理计算密集型任务

  6. UI定制作用域插槽 实现内容分发

相关推荐
谢尔登10 分钟前
【React Native】ScrollView 和 FlatList 组件
javascript·react native·react.js
蓝婷儿26 分钟前
每天一个前端小知识 Day 27 - WebGL / WebGPU 数据可视化引擎设计与实践
前端·信息可视化·webgl
然我33 分钟前
面试官:如何判断元素是否出现过?我:三种哈希方法任你选
前端·javascript·算法
OpenTiny社区1 小时前
告别代码焦虑,单元测试让你代码自信力一路飙升!
前端·github
kk_stoper1 小时前
如何通过API查询实时能源期货价格
java·开发语言·javascript·数据结构·python·能源
pe7er1 小时前
HTTPS:本地开发绕不开的设置指南
前端
晨枫阳1 小时前
前端VUE项目-day1
前端·javascript·vue.js
江山如画,佳人北望1 小时前
SLAM 前端
前端
患得患失9491 小时前
【前端】【Iconify图标库】【vben3】createIconifyIcon 实现图标组件的自动封装
前端
颜酱1 小时前
抽离ant-design后台的公共查询设置
前端·javascript·ant design