Vue3组合式API状态管理实战:Pinia与Composable的架构选型与性能调优

Vue3状态管理的选型困惑:Pinia vs Composable vs Vuex

Vue3的项目架构中,状态管理方案的选择比Vue2时代更复杂。Composition API提供了reactive和ref,让组件内部状态管理变得直观;Composable函数封装可复用逻辑,天然具备状态共享能力;Pinia作为Vuex的继任者,提供DevTools集成和SSR支持。三者的职责边界如果划分不清,项目会出现状态散落、重复请求、内存泄漏等一系列问题。

核心选型原则:

– 组件私有状态 → ref/reactive(不需要外部管理)
– 跨组件共享的业务逻辑 + 服务端数据 → Pinia Store
– 跨组件共享的纯前端交互逻辑(无持久化需求) → Composable
– 遗留项目升级 → Vuex 4(不推荐新项目使用)

Pinia Store的模块化架构设计

Pinia摒弃了Vuex的mutations,简化为state/getters/actions三层结构。模块化设计的关键是按业务领域划分Store,而非按页面划分:

// stores/user.ts
import { defineStore } from 'pinia'
import type { UserProfile, LoginParams } from '@/types'
import { userApi } from '@/api/user'

export const useUserStore = defineStore('user', () => {
  // State
  const profile = ref<UserProfile | null>(null)
  const token = ref<string>('')
  const permissions = ref<string[]>([])

  // Getters
  const isLoggedIn = computed(() => !!token.value)
  const hasPermission = (perm: string) => 
    computed(() => permissions.value.includes(perm))

  // Actions
  async function login(params: LoginParams) {
    const res = await userApi.login(params)
    token.value = res.token
    profile.value = res.user
    permissions.value = res.permissions
  }

  async function fetchProfile() {
    const res = await userApi.getProfile()
    profile.value = res
  }

  function logout() {
    token.value = ''
    profile.value = null
    permissions.value = []
  }

  return { profile, token, permissions, isLoggedIn, hasPermission, login, fetchProfile, logout }
})

Store间组合——一个Store可以引用另一个Store,避免状态重复:

// stores/order.ts
import { useUserStore } from './user'

export const useOrderStore = defineStore('order', () => {
  const userStore = useUserStore()
  const orders = ref<Order[]>([])

  const userOrders = computed(() => 
    orders.value.filter(o => o.userId === userStore.profile?.id)
  )

  return { orders, userOrders }
})

Composable函数的设计模式与边界

Composable函数适合封装可复用的交互逻辑,特点是:无全局状态、按需创建、自动清理。常见模式:

// composables/useRequest.ts — 通用请求封装
type RequestStatus = 'idle' | 'loading' | 'success' | 'error'

export function useRequest<T>(fetcher: () => Promise<T>) {
  const data = ref<T | null>(null) as Ref<T | null>
  const error = ref<Error | null>(null)
  const status = ref<RequestStatus>('idle')

  const isLoading = computed(() => status.value === 'loading')

  async function execute() {
    status.value = 'loading'
    error.value = null
    try {
      data.value = await fetcher()
      status.value = 'success'
    } catch (e) {
      error.value = e as Error
      status.value = 'error'
    }
  }

  return { data, error, status, isLoading, execute }
}

// 使用
const { data, isLoading, execute } = useRequest(() => api.getProducts())

Composable的生命周期管理——自动注册和清理副作用:

// composables/useWebSocket.ts
export function useWebSocket(url: string) {
  const messages = ref<Message[]>([])
  const isConnected = ref(false)
  let ws: WebSocket | null = null

  function connect() {
    ws = new WebSocket(url)
    ws.onopen = () => { isConnected.value = true }
    ws.onmessage = (e) => { 
      messages.value.push(JSON.parse(e.data)) 
    }
    ws.onclose = () => { isConnected.value = false }
  }

  function disconnect() {
    ws?.close()
    ws = null
  }

  // 关键:组件卸载时自动清理
  onUnmounted(() => {
    disconnect()
  })

  return { messages, isConnected, connect, disconnect }
}

Pinia持久化与SSR场景的状态水合

Pinia状态持久化到localStorage是常见需求,但需要注意序列化边界:

// plugins/pinia-persist.ts
import type { PiniaPluginContext } from 'pinia'

export function piniaPersist({ store }: PiniaPluginContext) {
  const key = `pinia-${store.$id}`
  
  // 从localStorage恢复
  const saved = localStorage.getItem(key)
  if (saved) {
    try {
      store.$patch(JSON.parse(saved))
    } catch (e) {
      console.warn(`Failed to parse persisted state for ${store.$id}`)
    }
  }

  // 监听变化并持久化(debounce避免频繁写入)
  let timer: number
  store.$subscribe((mutation, state) => {
    clearTimeout(timer)
    timer = setTimeout(() => {
      localStorage.setItem(key, JSON.stringify(state))
    }, 300)
  })
}

// 注册
const pinia = createPinia()
pinia.use(piniaPersist)

SSR场景下的状态水合(Hydration)是Vue3 + Pinia的难点:

// entry-server.ts
export async function render(url: string) {
  const { app, pinia } = createApp()
  const router = app.config.globalProperties.$router
  
  await router.push(url)
  await router.isReady()
  
  // SSR期间触发数据预取
  const userStore = useUserStore(pinia)
  await userStore.fetchProfile()
  
  const html = renderToString(app)
  const state = JSON.stringify(pinia.state.value)
  
  return { html, state }
}

// entry-client.ts
const { state } = window.__PINIA_STATE__
const pinia = createPinia()
pinia.state.value = state  // 水合服务端状态

app.use(pinia)
app.mount('#app')

性能优化:细粒度响应与计算属性缓存

Pinia Store中的computed属性天然具有缓存特性——依赖不变时不会重算。但React组件消费Pinia状态时,一个常见问题是不必要的重渲染:

// 问题:整个store变化都触发组件更新
const orderStore = useOrderStore()
// 模板中引用 orderStore.orders 导致任何store属性变化都重渲染

// 解决:精确订阅所需属性
const orders = computed(() => orderStore.orders)
const total = computed(() => 
  orderStore.orders.reduce((s, o) => s + o.amount, 0)
)

// 或使用storeToRefs避免响应式代理的扩散
import { storeToRefs } from 'pinia'
const { orders } = storeToRefs(orderStore)  // 只在orders变化时触发更新

大批量列表数据的虚拟滚动优化:

// composables/useVirtualList.ts
export function useVirtualList<T>(list: Ref<T[]>, itemHeight: number) {
  const containerHeight = ref(600)
  const scrollTop = ref(0)

  const visibleCount = computed(() => 
    Math.ceil(containerHeight.value / itemHeight)
  )

  const startIndex = computed(() => 
    Math.floor(scrollTop.value / itemHeight)
  )

  const endIndex = computed(() => 
    Math.min(startIndex.value + visibleCount.value, list.value.length)
  )

  const visibleItems = computed(() => 
    list.value.slice(startIndex.value, endIndex.value)
  )

  const totalHeight = computed(() => 
    list.value.length * itemHeight
  )

  const offsetY = computed(() => 
    startIndex.value * itemHeight
  )

  return { visibleItems, totalHeight, offsetY, scrollTop }
}

状态管理不是越复杂越好。Pinia + Composable的组合已经能覆盖Vue3项目99%的状态需求。关键是严格划分职责边界——Store管业务数据和服务端状态,Composable管交互逻辑和副作用,组件ref管UI局部状态。边界清晰了,性能问题和维护性问题都会大幅减少。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-zu-he-shi-api-zhuang-tai-guan-li-shi-zhan-pinia-yu/

(0)
小编小编
上一篇 4小时前
下一篇 4小时前

相关推荐