Vue3 Composition API状态管理实战:Pinia store设计模式与性能优化

Vue3的Composition API带来了全新的状态管理范式。Pinia作为Vue3官方推荐的状态管理库,替代Vuex成为新项目首选。相比Vuex,Pinia去除了mutations,提供更好的TypeScript支持,且支持模块化按需引入。本文通过实际项目案例演示Pinia store的设计模式、组合式用法和性能优化策略。

Pinia与Vuex的核心差异

Pinia的架构设计有三个关键改进。第一,移除mutations,直接在actions中修改state,代码更简洁。第二,Store天然模块化,无需modules嵌套,每个store独立文件。第三,完美的TypeScript推导,无需复杂的类型声明即可获得完整的类型提示。

安装Pinia:

npm install pinia
# 或
pnpm add pinia

// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const app = createApp(App)
app.use(createPinia())
app.mount('#app')

Setup Store写法:组合式API风格

Pinia支持两种Store定义风格:Options Store和Setup Store。Setup Store使用Composition API语法,灵活性更强,推荐在新项目中使用。

// stores/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { userApi } from '@/api/user'

export const useUserStore = defineStore('user', () => {
  // State
  const userInfo = ref<UserInfo | null>(null)
  const token = ref<string>(localStorage.getItem('token') || '')
  const permissions = ref<string[]>([])
  
  // Getters
  const isLoggedIn = computed(() => !!token.value)
  const userName = computed(() => userInfo.value?.name || '未登录')
  const hasPermission = computed(() => (perm: string) => 
    permissions.value.includes(perm) || permissions.value.includes('*')
  )
  
  // Actions
  async function login(credentials: LoginParams) {
    const res = await userApi.login(credentials)
    token.value = res.token
    localStorage.setItem('token', res.token)
    await fetchUserInfo()
    return res
  }
  
  async function fetchUserInfo() {
    if (!token.value) return
    const res = await userApi.getInfo()
    userInfo.value = res.user
    permissions.value = res.permissions
  }
  
  function logout() {
    userInfo.value = null
    token.value = ''
    permissions.value = []
    localStorage.removeItem('token')
  }
  
  return {
    userInfo, token, permissions,
    isLoggedIn, userName, hasPermission,
    login, fetchUserInfo, logout
  }
})

跨Store依赖与模块化组合

实际项目中多个Store之间常有依赖关系。Pinia允许在Store内部直接调用其他Store,无需复杂的模块注册。

// stores/cart.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { useUserStore } from './user'
import { productApi } from '@/api/product'

export const useCartStore = defineStore('cart', () => {
  const userStore = useUserStore()
  
  const items = ref<CartItem[]>([])
  const loading = ref(false)
  
  const total = computed(() => 
    items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
  )
  
  const itemCount = computed(() => 
    items.value.reduce((sum, item) => sum + item.quantity, 0)
  )
  
  async function addToCart(productId: number, quantity: number = 1) {
    if (!userStore.isLoggedIn) {
      throw new Error('请先登录')
    }
    loading.value = true
    try {
      const product = await productApi.getById(productId)
      const existing = items.value.find(i => i.productId === productId)
      if (existing) {
        existing.quantity += quantity
      } else {
        items.value.push({
          productId: product.id,
          name: product.name,
          price: product.price,
          quantity,
          thumb: product.thumb
        })
      }
    } finally {
      loading.value = false
    }
  }
  
  function removeFromCart(productId: number) {
    items.value = items.value.filter(i => i.productId !== productId)
  }
  
  return { items, loading, total, itemCount, addToCart, removeFromCart }
})

组件中使用Store的多重方式

Pinia在组件中的使用方式灵活,根据场景选择合适的模式。不同写法影响响应性追踪和代码可读性。

// 组件中使用
import { useUserStore } from '@/stores/user'
import { useCartStore } from '@/stores/cart'
import { storeToRefs } from 'pinia'

const userStore = useUserStore()
const cartStore = useCartStore()

// 方式1: 直接解构state会丢失响应性(错误示范)
// const { userInfo, token } = userStore  // 不推荐

// 方式2: 使用storeToRefs保持响应性(推荐)
const { userInfo, isLoggedIn, userName } = storeToRefs(userStore)
const { items, total, itemCount } = storeToRefs(cartStore)

// Actions可以直接解构,不需要storeToRefs
const { login, logout } = userStore
const { addToCart, removeFromCart } = cartStore

持久化插件:状态不丢失方案

默认Pinia状态仅存于内存,页面刷新即丢失。pinia-plugin-persistedstate插件实现自动持久化,支持localStorage、sessionStorage和自定义存储。

// 安装: npm install pinia-plugin-persistedstate
// main.ts
import { createPinia } from 'pinia'
import piniaPersist from 'pinia-plugin-persistedstate'

const pinia = createPinia()
pinia.use(piniaPersist)

// Store中配置持久化
export const useUserStore = defineStore('user', () => {
  // ...store内容
}, {
  persist: {
    key: 'app-user',
    storage: localStorage,
    pick: ['token', 'userInfo']
  }
})

// 多持久化目标
export const useAppStore = defineStore('app', () => {
  const theme = ref('light')
  const draftContent = ref('')
  const sidebarCollapsed = ref(false)
}, {
  persist: [
    {
      key: 'app-settings',
      storage: localStorage,
      pick: ['theme', 'sidebarCollapsed']
    },
    {
      key: 'app-draft',
      storage: sessionStorage,
      pick: ['draftContent']
    }
  ]
})

响应式性能优化:避免不必要的重新渲染

Pinia状态变化会触发所有依赖该状态的组件更新。大型应用中不当使用Store会导致性能问题。优化核心是缩小响应式追踪范围。

// 反面案例:整个list都响应式,任何修改触发全量更新
const list = ref<Product[]>([])
function updateOne(id: number, data: Partial<Product>) {
  list.value = list.value.map(item => 
    item.id === id ? { ...item, ...data } : item
  )
}

// 优化方案1: shallowRef + 手动触发
import { shallowRef, triggerRef } from 'vue'
const list = shallowRef<Product[]>([])
function updateOne(id: number, data: Partial<Product>) {
  const item = list.value.find(i => i.id === id)
  if (item) {
    Object.assign(item, data)
    triggerRef(list)
  }
}

// 优化方案2: 细粒度拆分Store,按功能模块独立
// 将大的Store拆分为productList、productDetail等独立Store

// 组件层面优化:只订阅需要的字段
const expensiveList = computed(() => 
  list.value.filter(p => p.status === 'active')
)

针对大型列表场景,配合前端工程化最佳实践:使用shallowRef处理大数据集;虚拟滚动渲染大量列表项;分页加载减少单次数据量。组件库设计时,将Store的订阅粒度尽量细化,每个组件只订阅自己需要的切片数据。

Pinia的优势在于简洁的API和优秀的TypeScript实战体验。配合合理的Store拆分策略和响应式优化,能支撑大型Vue3应用的复杂状态管理需求。关键原则是保持Store职责单一、按功能域拆分、避免全局Store膨胀。

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

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

相关推荐