网站首页 全球最实用的IT互联网站!

人工智能P2P分享Wind搜索发布信息网站地图标签大全

当前位置:诺佳网 > 软件工程 > 前端开发 > Vue >

Vue 3 组件通信的 4 种正确姿势

时间:2026-03-04 14:46

人气:

作者:admin

标签:

导读:#129489;‍#128187; 写在开头 点赞 收藏 学会#129315;#129315;#129315; 上个月,我们重构一个老项目,发现一个“祖传组件”: 父组件传 props 给子组件 子组件再传给孙子 孙子改了个状态,通过...

点赞 + 收藏 === 学会????????????

上个月,我们重构一个老项目,发现一个“祖传组件”:

  • 父组件传 props 给子组件
  • 子组件再传给孙子
  • 孙子改了个状态,通过 $emit 一层层往上抛
  • 中间任意一层改名,整条链就断了……

同事苦笑:“这哪是组件通信,这是传话游戏。”

其实,Vue 3 早就提供了更优雅、更健壮的通信方案。
今天我就用 4 种场景 + 对应解法,帮你彻底告别“props drilling”和“emit 地狱”。


先看一张决策图(建议收藏)

ScreenShot_2026-03-04_144140_872 - 副本

 

注意:不是所有通信都要用 Pinia! 小范围状态用轻量方案更干净。


姿势 1:父子通信 —— 老老实实用 props/emit(但要规范)

这是最基础的,但很多人写得乱:

反面教材:

<!-- Child.vue -->
<script setup>
const emit = defineEmits(['update-name', 'save', 'cancel', 'validate']);
// 4 个 emit?这个组件到底负责什么?
</script>

正确做法:单一职责 + 语义化命名

<!-- UserForm.vue -->
<script setup>
const props = defineProps<{ modelValue: string }>();
const emit = defineEmits<{ (e: 'update:modelValue', val: string): void }>();

const localValue = ref(props.modelValue);
watch(() => props.modelValue, v => localValue.value = v);

const handleChange = () => {
  emit('update:modelValue', localValue.value); // 使用 v-model 语法糖
};
</script>

技巧:用 v-model 代替自定义 update-xxx,模板更简洁:

<UserForm v-model="userName" />

姿势 2:祖孙通信 —— 用 provide / inject 跳过中间层

当你需要从 App.vue 直接传数据到深度嵌套的 Button 组件,别再层层传 props!

// App.vue
import { provide, ref } from 'vue';

const theme = ref<'light' | 'dark'>('light');
provide('THEME', theme); // 提供响应式数据
<!-- DeepChildButton.vue -->
<script setup>
import { inject } from 'vue';

const theme = inject('THEME'); // 自动获得响应性!
</script>

<template>
  <button :class="theme">Click me</button>
</template>

  关键点:

  • 如果 provide 的是 ref 或 reactiveinject 拿到的就是响应式的
  • 可以配合 TypeScript 定义 InjectionKey,避免字符串魔法值
// types.ts
import { InjectionKey, Ref } from 'vue';
export const THEME_KEY: InjectionKey<Ref<'light' | 'dark'>> = Symbol('theme');

姿势 3:任意组件通信 —— 用 Composable 封装共享状态(90% 的人不知道!)

这是 Vue 3 最被低估的能力!

想象:两个不相关的弹窗,需要共享“是否正在提交”状态

错误做法:把状态提到父组件,或滥用 Pinia

正确做法:写一个 useSubmitState composable:

// composables/useSubmitState.ts
import { ref } from 'vue';

const isSubmitting = ref(false);

export function useSubmitState() {
  const start = () => isSubmitting.value = true;
  const end = () => isSubmitting.value = false;

  return { isSubmitting, start, end };
}

然后在任意组件中使用:

<!-- ModalA.vue -->
<script setup>
import { useSubmitState } from '@/composables/useSubmitState';
const { isSubmitting, start } = useSubmitState();

const handleSubmit = () => {
  start();
  // ...提交逻辑
};
</script>
<!-- ModalB.vue -->
<script setup>
import { useSubmitState } from '@/composables/useSubmitState';
const { isSubmitting } = useSubmitState(); // 实时同步!
</script>

  优势:

  • 零依赖(不用 Pinia)
  • 天然响应式
  • 可测试、可复用
  • 作用域清晰(只在需要的组件引入)

姿势 4:全局状态 —— 交给 Pinia,别自己造轮子

当状态涉及:

  • 用户登录信息
  • 全局主题/语言
  • 跨路由的数据缓存

这时候就该用 Pinia(Vuex 的继任者,Vue 官方推荐):

// stores/user.ts
import { defineStore } from 'pinia';

export const useUserStore = defineStore('user', () => {
  const profile = ref(null);
  const isLoggedIn = computed(() => !!profile.value);

  const login = async (credentials) => {
    profile.value = await api.login(credentials);
  };

  return { profile, isLoggedIn, login };
});

在组件中:

const userStore = useUserStore();
userStore.login({ email, password });

  Pinia 优势:

  • Composition API 风格
  • 完美 TS 支持
  • DevTools 调试友好
  • 服务端渲染(SSR)兼容

总结:什么时候用哪种?

ScreenShot_2026-03-04_144507_635

 不要:

  • 用 $parent / $children(破坏封装)
  • 用 EventBus(Vue 3 已废弃)
  • 所有状态都塞进 Pinia(过度设计)

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

温馨提示:以上内容整理于网络,仅供参考,如果对您有帮助,留下您的阅读感言吧!
相关阅读
本类排行
相关标签
本类推荐

CPU | 内存 | 硬盘 | 显卡 | 显示器 | 主板 | 电源 | 键鼠 | 网站地图

Copyright © 2025-2035 诺佳网 版权所有 备案号:赣ICP备2025066733号
本站资料均来源互联网收集整理,作品版权归作者所有,如果侵犯了您的版权,请跟我们联系。

关注微信