Vue3组合式API状态管理:Pinia与Composable的分层架构设计

Pinia不是Vuex的简单替代品

Vue3生态中,Pinia取代Vuex成为官方推荐的状态管理方案,这不是换个API写法的事。Pinia去掉了Vuex的mutation概念,支持Composition API风格定义store,天然支持TypeScript类型推导,且支持多个store实例独立运行。但对于中大型前端项目,光有Pinia不够——Composable函数承担了组件逻辑复用的职责,和Pinia的职责边界需要明确划分,否则代码会陷入什么都往store里塞或到处都是composable的两个极端。

前端工程化的核心挑战之一就是状态管理架构的分层设计。合理的分层让代码定位有规律可循,组件只关心视图逻辑。

三层架构:Component到Composable到Store

这三层的职责划分:

Component:纯视图逻辑,模板渲染、用户交互事件、样式计算

Composable:业务逻辑封装,API请求编排、数据转换、跨组件状态协调

Store:全局共享状态,跨页面持久数据、用户会话信息、全局配置

判断标准:状态是否需要跨路由页面共享?需要则Store;只跨组件共享则Composable;只在当前组件内则ref/reactive留在Component。

Pinia Store的设计规范

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

export const useUserStore = defineStore('user', () => {
  const userInfo = ref<UserInfo | null>(null)
  const token = ref<string>('')
  const permissions = ref<string[]>([])
  
  const isLoggedIn = computed(() => !!token.value)
  const hasPermission = computed(() => {
    return (perm: string) => permissions.value.includes(perm)
  })
  
  async function login(params: LoginParams) {
    const { data } = await userApi.login(params)
    token.value = data.token
    userInfo.value = data.user
    permissions.value = data.permissions
    localStorage.setItem('token', data.token)
  }
  
  async function logout() {
    await userApi.logout()
    $reset()
    localStorage.removeItem('token')
  }
  
  function $reset() {
    userInfo.value = null
    token.value = ''
    permissions.value = []
  }
  
  return { userInfo, token, permissions, isLoggedIn, hasPermission, login, logout, $reset }
})

Composable函数封装业务逻辑

// composables/useUserList.ts
import { ref, reactive } from 'vue'
import { userApi } from '@/api/user'

export function useUserList() {
  const loading = ref(false)
  const list = ref([])
  const pagination = reactive({ page: 1, pageSize: 20, total: 0 })
  const searchParams = reactive({ keyword: '', status: undefined })
  
  async function fetchList() {
    loading.value = true
    try {
      const { data } = await userApi.getList({
        page: pagination.page, pageSize: pagination.pageSize,
        ...searchParams
      })
      list.value = data.list
      pagination.total = data.total
    } finally { loading.value = false }
  }
  
  function handlePageChange(page) { pagination.page = page; fetchList() }
  function handleSearch() { pagination.page = 1; fetchList() }
  
  fetchList()
  return { loading, list, pagination, searchParams, fetchList, handlePageChange, handleSearch }
}

组件中的调用方式

<script setup lang="ts">
import { useUserList } from '@/composables/useUserList'
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()
const { loading, list, pagination, searchParams, handlePageChange, handleSearch } = useUserList()
</script>

组件代码极简——只做数据绑定和事件传递,业务逻辑全部在composable中,全局状态在store中。

跨端小程序开发的适配策略

uni-app + Vue3场景下,Pinia和Composable的架构同样适用,但需要注意小程序环境的限制:

// composables/useMiniAppAuth.ts
import { ref } from 'vue'
import { useUserStore } from '@/stores/user'

export function useMiniAppAuth() {
  const userStore = useUserStore()
  const authorizing = ref(false)
  
  async function authorize() {
    authorizing.value = true
    try {
      // #ifdef MP-WEIXIN
      const { code } = await uni.login({ provider: 'weixin' })
      await userStore.loginByCode(code)
      // #endif
      
      // #ifdef H5
      const redirect = encodeURIComponent(window.location.href)
      window.location.href = '/auth/wechat?redirect=' + redirect
      // #endif
    } finally { authorizing.value = false }
  }
  
  return { authorizing, authorize }
}

TypeScript类型安全与Store间协作

// stores/app.ts
import { defineStore } from 'pinia'
import { useUserStore } from './user'
import { usePermissionStore } from './permission'

export const useAppStore = defineStore('app', () => {
  const initialized = ref(false)
  
  async function initApp() {
    const userStore = useUserStore()
    const permissionStore = usePermissionStore()
    
    const token = localStorage.getItem('token')
    if (!token) { initialized.value = true; return }
    
    userStore.token = token
    await permissionStore.fetchRoutes()
    initialized.value = true
  }
  
  return { initialized, initApp }
})

Store之间通过函数内部useXxxStore()调用实现依赖,避免模块级别的循环引用。

Web性能优化:Pinia的响应式追踪开销

大型项目中store字段过多时,Pinia的响应式追踪会产生不必要的计算开销:

storeToRefs按需解构:只解构组件需要的字段,避免整个store的响应式追踪

shallowRef处理大对象:列表数据不需要深层响应式时用shallowRef

computed缓存:派生状态用computed而非方法调用

// 优化前:整store响应式追踪
const userStore = useUserStore()

// 优化后:只追踪用到的字段
const { userInfo, isLoggedIn } = storeToRefs(useUserStore())

// 大列表用shallowRef
const bigList = shallowRef([])
bigList.value = await fetchBigList()

这套分层架构在多个Vue3项目中落地验证,10万行级别的前端代码库依然能保持清晰的模块边界和可维护性。组件文件平均不超过80行,composable函数职责单一,store只管全局共享数据。

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

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

相关推荐