Appearance
组件通信与刷新实现方式对比
实现方式对比表
| 方式 | 实现原理 | 优点 | 缺点 | 适用场景 | 代码复杂度 |
|---|---|---|---|---|---|
| 1. 父组件调用子组件方法<br/>(ref + defineExpose) | 父组件通过 ref 获取子组件实例,调用子组件暴露的方法 | • 直接明确<br/>• 类型安全(TypeScript)<br/>• 调用时机可控 | • 强耦合(父组件需要知道子组件方法名)<br/>• 需要维护 ref<br/>• 违反单向数据流原则 | • 需要精确控制调用时机<br/>• 子组件方法需要被外部调用 | ⭐⭐⭐ |
| 2. 子组件监听父组件状态<br/>(watch props) | 父组件通过 props 传递状态变化,子组件 watch 监听 | • 符合单向数据流<br/>• 解耦(父组件只需更新状态)<br/>• 响应式自动触发<br/>• 易于测试 | • 需要设计合适的状态标识<br/>• 可能触发不必要的刷新 | • 状态驱动的刷新<br/>• 多个子组件需要同步刷新 | ⭐⭐ |
| 3. 事件总线/全局状态<br/>(EventBus/Pinia) | 通过全局事件或状态管理触发刷新 | • 完全解耦<br/>• 跨组件通信<br/>• 支持一对多 | • 难以追踪数据流<br/>• 可能造成性能问题<br/>• 调试困难 | • 跨层级通信<br/>• 多个不相关组件需要同步 | ⭐⭐⭐⭐ |
| 4. Provide/Inject<br/>(依赖注入) | 父组件 provide 刷新函数,子组件 inject 使用 | • 跨层级传递<br/>• 避免 prop drilling<br/>• 类型安全 | • 依赖关系不明确<br/>• 可能滥用<br/>• 调试困难 | • 深层嵌套组件<br/>• 多个子组件需要同一功能 | ⭐⭐⭐ |
| 5. Props 传递回调函数<br/>(callback props) | 父组件传递回调函数,子组件在适当时机调用 | • 简单直接<br/>• 符合 React 模式<br/>• 易于理解 | • 需要传递函数引用<br/>• 可能造成不必要的重渲染 | • 简单的父子通信<br/>• 需要双向通信 | ⭐⭐ |
| 6. 组合式函数返回刷新函数<br/>(composable pattern) | 通过 composable 返回刷新函数,父子组件共享 | • 逻辑复用<br/>• 统一管理<br/>• 易于测试 | • 需要合理设计 composable<br/>• 可能过度设计 | • 复杂的状态管理<br/>• 需要逻辑复用 | ⭐⭐⭐ |
详细代码示例
方式 1: 父组件调用子组件方法 (ref + defineExpose)
父组件 (OrderTabs.vue)
vue
<template>
<ClosedOrder ref="closeOrderRef" ... />
</template>
<script setup>
const closeOrderRef = ref();
const { refresh } = useClosePosition();
// 刷新时调用子组件方法
const handleRefresh = () => {
refresh();
closeOrderRef.value?.fetchCloseOrdersSum(); // 手动调用
};
</script>子组件 (CloseOrder.vue)
vue
<script setup>
const fetchCloseOrdersSum = () => {
fetchSum({ ... })
}
defineExpose({
fetchCloseOrdersSum, // 暴露方法供父组件调用
})
</script>方式 2: 子组件监听父组件状态 (当前实现) ✅
父组件 (OrderTabs.vue)
vue
<template>
<ClosedOrder :refresh-key="closedOrderRefreshKey" ... />
</template>
<script setup>
const { refresh, refreshKey } = useClosePosition();
// 只需更新状态,子组件自动响应
const handleRefresh = () => {
refresh(); // refreshKey 自动递增
};
</script>子组件 (CloseOrder.vue)
vue
<script setup>
const props = defineProps<{
refreshKey?: number
}>()
watch(
() => props.refreshKey,
(newKey, oldKey) => {
if (newKey !== undefined && oldKey !== undefined && newKey !== oldKey) {
fetchCloseOrdersSum() // 自动响应状态变化
}
},
)
</script>方式 3: 事件总线/全局状态
使用 Pinia Store
typescript
// store/refresh.ts
export const useRefreshStore = defineStore("refresh", () => {
const refreshKey = ref(0);
const triggerRefresh = () => {
refreshKey.value++;
};
return { refreshKey, triggerRefresh };
});父组件
vue
<script setup>
const refreshStore = useRefreshStore();
const handleRefresh = () => {
refreshStore.triggerRefresh();
};
</script>子组件
vue
<script setup>
const refreshStore = useRefreshStore();
watch(
() => refreshStore.refreshKey,
() => {
fetchCloseOrdersSum();
}
);
</script>方式 4: Provide/Inject
父组件
vue
<script setup>
const refreshKey = ref(0);
const triggerRefresh = () => {
refreshKey.value++;
};
provide("refreshCloseOrder", {
refreshKey: readonly(refreshKey),
triggerRefresh,
});
</script>子组件
vue
<script setup>
const { refreshKey, triggerRefresh } = inject('refreshCloseOrder')!
watch(
() => refreshKey.value,
() => {
fetchCloseOrdersSum()
},
)
</script>方式 5: Props 传递回调函数
父组件
vue
<template>
<ClosedOrder :on-refresh="handleRefresh" ... />
</template>
<script setup>
const handleRefresh = () => {
// 父组件的刷新逻辑
refresh();
};
</script>子组件
vue
<script setup>
const props = defineProps<{
onRefresh?: () => void
}>()
// 在需要时调用
const someAction = () => {
props.onRefresh?.()
}
</script>方式 6: 组合式函数返回刷新函数
Composable (useClosePosition.ts)
typescript
export function useClosePosition() {
const refreshKey = ref(0);
// 统一的刷新函数
const refreshAll = () => {
refreshKey.value++;
// 可以在这里统一处理所有刷新逻辑
};
return {
refreshKey,
refreshAll,
// ... 其他
};
}父组件
vue
<script setup>
const { refreshAll } = useClosePosition();
const handleRefresh = () => {
refreshAll(); // 统一刷新
};
</script>子组件
vue
<script setup>
// 可以共享同一个 composable 实例
// 或者通过 props 传递 refreshKey
</script>推荐选择指南
🎯 当前场景推荐:方式 2 (子组件监听状态)
理由:
- ✅ 符合 Vue 3 响应式设计理念
- ✅ 单向数据流,易于维护
- ✅ 父组件只需关注状态管理,无需关心子组件实现
- ✅ 代码简洁,易于理解
📊 其他场景推荐
| 场景 | 推荐方式 | 理由 |
|---|---|---|
| 需要精确控制调用时机 | 方式 1 (ref) | 直接调用,时机可控 |
| 跨层级、跨组件通信 | 方式 3 (Store) | 全局状态,解耦彻底 |
| 深层嵌套组件 | 方式 4 (Provide/Inject) | 避免 prop drilling |
| 简单父子交互 | 方式 5 (Callback) | 简单直接 |
| 复杂状态管理 | 方式 6 (Composable) | 逻辑复用,统一管理 |
性能对比
| 方式 | 性能开销 | 重渲染次数 | 内存占用 |
|---|---|---|---|
| ref + defineExpose | 低 | 1 次 | 低 |
| watch props | 低 | 1-2 次 | 低 |
| EventBus/Store | 中 | 可能多次 | 中 |
| Provide/Inject | 低 | 1 次 | 低 |
| Callback Props | 低 | 可能多次 | 低 |
| Composable | 低 | 1 次 | 低 |
总结
最佳实践建议:
- 优先使用方式 2(状态驱动):符合 Vue 3 设计理念,代码清晰
- 简单场景用方式 5(回调函数):直接明了
- 复杂场景用方式 6(Composable):逻辑复用,易于维护
- 跨组件用方式 3(Store):全局状态管理
- 避免过度使用方式 1(ref):除非确实需要精确控制
当前实现的优势:
- 🎯 符合单向数据流原则
- 🎯 父组件无需关心子组件内部实现
- 🎯 响应式自动触发,无需手动调用
- 🎯 易于测试和维护
- 🎯 支持多个子组件同时响应