2.Vue 组件通信 - 标准答案

考察点:方案选型、状态管理、性能优化、架构设计

场景描述: 一个电商后台管理系统,包含:

  • 多层级的嵌套路由(3-5层)
  • 全局状态(用户信息、权限、主题配置)
  • 跨模块的数据共享(订单模块 ↔ 库存模块 ↔ 财务模块)
  • 实时数据更新(WebSocket 推送的订单状态变更)

问题

  1. 方案选型props/emitprovide/injectVuexPiniaEventBus 这些方案,您会如何组合使用?
  2. 状态管理:Vuex vs Pinia,在 Vue3 项目中您会选择哪个?为什么?
  3. 性能优化:当状态树很大时,如何避免不必要的组件重渲染?
  4. 实战经验:您在项目中遇到过哪些组件通信的难题?

方案适用场景优点缺点
props/emit父子组件直接通信简单直接、类型安全层级多时繁琐
provide/inject跨层级传递配置、依赖无需逐层传递不适合响应式数据
Pinia全局状态、跨组件共享组合式API、TS支持好学习成本
EventBus跨组件事件通信灵活难以维护、Vue3已移除

推荐组合方案

// 1. 父子组件:props/emit
<ChildComponent :data="parentData" @update="handleUpdate" />

// 2. 跨层级配置:provide/inject
// 父组件
provide('theme', computed(() => theme.value))

// 子孙组件
const theme = inject('theme')

// 3. 全局状态:Pinia
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', () => {
  const user = ref(null)
  const isLoggedIn = computed(() => !!user.value)
  
  function login(credentials) {
    // ...
  }
  
  return { user, isLoggedIn, login }
})

// 4. 实时更新:Pinia + WebSocket
export const useOrderStore = defineStore('order', () => {
  const orders = ref([])
  
  onMounted(() => {
    const ws = new WebSocket('ws://...')
    ws.onmessage = (event) => {
      const order = JSON.parse(event.data)
      updateOrder(order)
    }
  })
  
  return { orders }
})

选择 Pinia 的理由

  1. 更好的 TypeScript 支持
// Pinia - 自动类型推导
export const useStore = defineStore('main', () => {
  const count = ref(0)
  const doubleCount = computed(() => count.value * 2)
  function increment() {
    count.value++
  }
  return { count, doubleCount, increment }
})

// 使用时有完整类型提示
const store = useStore()
store.count  // number
store.doubleCount  // number
store.increment()  // void
  1. 组合式 API 风格(更符合 Vue3)
  2. 无需 mutations(简化代码)
  3. 支持多实例(适合SSR和多标签页场景)
  4. 更轻量(~1KB)

核心差异

特性VuexPinia
API 风格Options APIComposition API
Mutations必需无需(直接修改state)
TypeScript需手动声明自动推导
DevTools支持支持
模块化需配置 modules天然支持多 store
SSR支持更好的支持

避免不必要的重渲染

// 1. 使用 storeToRefs 解构响应式属性
import { storeToRefs } from 'pinia'

const store = useStore()
const { count, doubleCount } = storeToRefs(store)  // 保持响应式
const { increment } = store  // 方法不需要响应式

// 2. 选择性订阅(只监听需要的状态)
const orderStore = useOrderStore()

// ❌ 不好:订阅整个store
watch(() => orderStore.$state, () => {
  // 任何状态变化都会触发
})

// ✅ 好:只监听特定状态
watch(() => orderStore.orders, (newOrders) => {
  // 只在orders变化时触发
})

// 3. 使用计算属性过滤数据
const filteredOrders = computed(() => {
  return orderStore.orders.filter(order => order.status === 'pending')
})

// 4. 虚拟滚动处理大列表
import { useVirtualList } from '@vueuse/core'

const { list, containerProps, wrapperProps } = useVirtualList(
  largeList,
  { itemHeight: 50 }
)

常见问题与解决方案

问题1:多标签页状态隔离

// 解决方案:多实例 Pinia
import { createPinia, setActivePinia } from 'pinia'

// 每个标签页创建独立的 pinia 实例
const pinia = createPinia()
setActivePinia(pinia)

app.use(pinia)

问题2:跨模块数据同步

// 订单store监听库存store变化
export const useOrderStore = defineStore('order', () => {
  const inventoryStore = useInventoryStore()
  
  watch(() => inventoryStore.stock, (newStock) => {
    // 库存变化时更新订单状态
    updateOrdersAvailability(newStock)
  })
  
  return { orders }
})

问题3:组件卸载后状态清理

export const useFormStore = defineStore('form', () => {
  const formData = ref({})
  
  function reset() {
    formData.value = {}
  }
  
  // 组件卸载时自动清理
  onUnmounted(() => {
    reset()
  })
  
  return { formData, reset }
})

  1. 优先使用 props/emit(父子组件)
  2. Pinia 管理全局状态(用户、权限、主题)
  3. provide/inject 传递依赖(配置、服务实例)
  4. 避免 EventBus(难以维护,Vue3已移除)
  5. 状态持久化:使用 pinia-plugin-persistedstate
  6. 状态分模块:按业务领域拆分 store
  7. 性能优化:使用 storeToRefs、计算属性、虚拟滚动