Vue3状态管理的两种主流范式
Vue3的组合式API(Composition API)为状态管理带来了新的实现范式。除了延续Vue2生态的Vuex/Pinia集中式状态管理,composable函数提供了一种基于组合的轻量级状态管理方案。两种范式各有适用场景,选型不当会导致代码冗余或架构混乱。本文从实际工程需求出发,对比Pinia与composable函数的差异,给出明确的选型建议和代码实践。
Pinia:集中式状态管理的标准方案
Pinia是Vue3官方推荐的状态管理库,支持组合式API写法,内置TypeScript类型推导、DevTools集成和SSR支持。Pinia的核心优势在于跨组件的状态共享和持久化、时间旅行调试、插件扩展。
以下为Pinia组合式写法的典型示例,实现用户认证状态管理:
// stores/auth.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useAuthStore = defineStore('auth', () => {
const token = ref<string | null>(null)
const user = ref<{ id: string; name: string; role: string } | null>(null)
const isAuthenticated = computed(() => !!token.value)
const isAdmin = computed(() => user.value?.role === 'admin')
async function login(username: string, password: string) {
const res = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
})
const data = await res.json()
token.value = data.token
user.value = data.user
localStorage.setItem('auth_token', data.token)
}
function logout() {
token.value = null
user.value = null
localStorage.removeItem('auth_token')
}
return { token, user, isAuthenticated, isAdmin, login, logout }
})
Pinia的插件机制支持状态持久化、数据快照等增强能力:
// main.ts
import { createPinia } from 'pinia'
const pinia = createPinia()
pinia.use(({ store }) => {
// 状态变更日志插件
store.$onAction(({ name, after }) => {
after(() => {
console.log(`[Pinia] ${store.$id}.${name} completed`)
})
})
})
Composable函数:轻量级局部状态管理
composable函数利用Vue3的响应式系统(ref、reactive、computed),在组件外部封装可复用的状态逻辑。与Pinia不同,composable函数默认不跨组件共享状态——每次调用创建独立的响应式实例,除非显式使用模块级变量实现单例模式。
以下为使用composable函数实现相同认证逻辑的示例:
// composables/useAuth.ts
import { ref, computed, readonly } from 'vue'
// 模块级变量,实现跨组件共享
const token = ref<string | null>(null)
const user = ref<{ id: string; name: string; role: string } | null>(null)
export function useAuth() {
const isAuthenticated = computed(() => !!token.value)
const isAdmin = computed(() => user.value?.role === 'admin')
async function login(username: string, password: string) {
const res = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
})
const data = await res.json()
token.value = data.token
user.value = data.user
localStorage.setItem('auth_token', data.token)
}
function logout() {
token.value = null
user.value = null
localStorage.removeItem('auth_token')
}
return {
token: readonly(token),
user: readonly(user),
isAuthenticated,
isAdmin,
login,
logout
}
}
关键区别:composable函数中token和user声明在模块顶层,所有调用useAuth()的组件共享同一份响应式数据。通过readonly包装导出,防止外部直接修改,实现单向数据流。但这种模式缺少Pinia的DevTools集成、插件系统和SSR兼容性。
两种范式的核心差异对比
状态共享方式。Pinia通过全局store注册实现跨组件共享,每个store有唯一ID,DevTools可直接追踪状态变更。composable函数通过模块级变量或provide/inject实现共享,缺少标准化的调试手段。
TypeScript支持。Pinia的组合式写法天然支持类型推导,无需手动声明类型。composable函数同样支持,但在共享模式下类型标注需要额外处理。
SSR兼容性。Pinia内置SSR支持,避免跨请求状态污染。composable函数在SSR场景下需要手动处理模块级变量的隔离,否则不同请求可能读取到其他请求的状态数据。
测试便利性。Pinia提供createTestingPinia辅助函数,可mock store行为。composable函数的测试需要手动mock模块依赖。
工程选型决策框架
根据实际项目特征,选型决策遵循以下规则:
1. 全局共享状态(用户信息、权限、主题、国际化)→ Pinia。这类状态需要跨路由、跨模块访问,DevTools调试和SSR支持是刚需。
2. 局部功能状态(表单数据、列表筛选、分页)→ composable函数。这类状态仅在少数组件间共享,引入Pinia store增加了不必要的架构复杂度。
3. 第三方集成状态(WebSocket连接、地图实例)→ composable函数 + provide/inject。这类状态有生命周期管理需求,composable函数的onUnmounted清理机制更自然。
4. 持久化状态(用户偏好、草稿缓存)→ Pinia + 持久化插件。Pinia的插件机制可统一处理localStorage/sessionStorage同步逻辑,避免每个composable重复实现。
在大型项目中,两种范式可以共存。全局状态使用Pinia管理,局部功能使用composable封装。关键是建立团队约定,明确哪些状态归Pinia管理、哪些归composable管理,避免同一功能在两种范式中重复实现导致数据同步混乱。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-zu-he-shi-api-zhuang-tai-guan-li-mo-shi-dui-bi-pinia/