时间:2026-03-04 14:46
人气:
作者:admin
点赞 + 收藏 === 学会????????????
上个月,我们重构一个老项目,发现一个“祖传组件”:
$emit 一层层往上抛同事苦笑:“这哪是组件通信,这是传话游戏。”
其实,Vue 3 早就提供了更优雅、更健壮的通信方案。
今天我就用 4 种场景 + 对应解法,帮你彻底告别“props drilling”和“emit 地狱”。

注意:不是所有通信都要用 Pinia! 小范围状态用轻量方案更干净。
这是最基础的,但很多人写得乱:
反面教材:
<!-- 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" />
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或reactive,inject拿到的就是响应式的- 可以配合 TypeScript 定义 InjectionKey,避免字符串魔法值
// types.ts
import { InjectionKey, Ref } from 'vue';
export const THEME_KEY: InjectionKey<Ref<'light' | 'dark'>> = Symbol('theme');
这是 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)
- 天然响应式
- 可测试、可复用
- 作用域清晰(只在需要的组件引入)
当状态涉及:
这时候就该用 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)兼容

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