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/