Vue3生态中Pinia已全面取代Vuex成为官方推荐的状态管理方案。相比Vuex,Pinia移除了mutation概念,API设计更贴合Composition API风格,TypeScript类型推导也更完善。前端开发中状态管理的核心需求包括跨组件数据共享、异步数据流处理、状态持久化和模块化拆分。Pinia通过简洁的defineStore API和插件机制覆盖了这些场景,bundle size仅约1.5KB(gzipped),对前端工程化项目的侵入性极低。
Pinia安装与Store定义
Pinia的安装和初始化只需三行代码。Store定义支持两种语法风格:Options Store(类似Vuex的state/getters/actions结构)和Setup Store(Composition API风格)。实际项目中推荐使用Setup Store,类型推导和代码组织更灵活。
// 安装
// npm install pinia @pinia/nuxt
// main.ts 初始化
import { createApp } from 'vue'
import { createPinia } from 'pinia'
const app = createApp(App)
app.use(createPinia())
app.mount('#app')
// Setup Store 定义
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useUserStore = defineStore('user', () => {
// state
const token = ref('')
const userInfo = ref(null)
const permissions = ref([])
// getters (computed)
const isLoggedIn = computed(() => !!token.value)
const isAdmin = computed(() => permissions.value.includes('admin'))
const displayName = computed(() => {
if (!userInfo.value) return '未登录'
return userInfo.value.nickname || userInfo.value.username
})
// actions
async function login(credentials: LoginParams) {
const res = await authApi.login(credentials)
token.value = res.token
userInfo.value = res.user
permissions.value = res.permissions
return res
}
function logout() {
token.value = ''
userInfo.value = null
permissions.value = []
router.push('/login')
}
function hasPermission(perm: string): boolean {
return permissions.value.includes(perm)
}
return {
token, userInfo, permissions,
isLoggedIn, isAdmin, displayName,
login, logout, hasPermission,
}
})
Setup Store中,ref对应state,computed对应getters,普通函数对应actions。这种方式的好处是可以在Store内部自由使用其他Composition API(watch、watchEffect、provide/inject),而不受Options API的结构限制。Store的返回值既包含state也包含derived state和方法,组件中解构使用时所有响应性都自动保留。
Store模块化设计与组合式API
大型前端项目的状态管理需要按业务域拆分Store,Pinia的模块化天然支持——每个defineStore调用就是一个独立模块,模块间通过Store组合实现跨域状态依赖。无需Vuex的modules嵌套配置,各Store之间可直接引用。
// stores/cart.ts - 购物车Store
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { useUserStore } from './user'
export const useCartStore = defineStore('cart', () => {
const userStore = useUserStore() // 跨Store引用
const items = ref([])
const couponCode = ref('')
// 跨Store计算
const totalPrice = computed(() => {
const subtotal = items.value.reduce((sum, item) => {
return sum + item.price * item.quantity
}, 0)
// 根据用户会员等级计算折扣
const discount = userStore.isAdmin ? 0.85 : 1.0
return subtotal * discount
})
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('请先登录')
}
const product = await productApi.getProduct(productId)
const existing = items.value.find(i => i.productId === productId)
if (existing) {
existing.quantity += quantity
} else {
items.value.push({ productId, quantity, price: product.price, name: product.name })
}
}
function removeFromCart(productId: number) {
items.value = items.value.filter(i => i.productId !== productId)
}
return { items, couponCode, totalPrice, itemCount, addToCart, removeFromCart }
})
// stores/order.ts - 订单Store依赖购物车Store
import { defineStore } from 'pinia'
import { useCartStore } from './cart'
import { useUserStore } from './user'
export const useOrderStore = defineStore('order', () => {
const cartStore = useCartStore()
const userStore = useUserStore()
const orders = ref([])
const creating = ref(false)
async function createOrder() {
if (cartStore.items.length === 0) return
creating.value = true
try {
const order = await orderApi.create({
userId: userStore.userInfo!.id,
items: cartStore.items,
totalAmount: cartStore.totalPrice,
couponCode: cartStore.couponCode,
})
orders.value.unshift(order)
cartStore.items = [] // 清空购物车
return order
} finally {
creating.value = false
}
}
return { orders, creating, createOrder }
})
Store组合时需要注意循环依赖问题。如果Store A引用Store B,Store B又引用Store A,会导致初始化时死锁。解决方案是将共享逻辑提取到独立的composable函数中,两个Store都引用该composable而非直接互相引用。
持久化存储插件配置
Pinia的状态默认存储在内存中,页面刷新后丢失。对于需要持久化的状态(如用户token、主题偏好、购物车数据),使用pinia-plugin-persistedstate插件实现自动持久化,支持localStorage、sessionStorage和自定义存储后端。
// 安装
// npm install pinia-plugin-persistedstate
// main.ts 注册插件
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
// Store中配置持久化
export const useUserStore = defineStore('user', () => {
const token = ref('')
const theme = ref<'light' | 'dark'>('light')
const sidebarCollapsed = ref(false)
// ... 其他非持久化状态
const tempFormData = ref({})
return { token, theme, sidebarCollapsed, tempFormData }
}, {
persist: {
// 只持久化指定字段
pick: ['token', 'theme', 'sidebarCollapsed'],
// 或使用paths兼容旧版
// paths: ['token', 'theme', 'sidebarCollapsed'],
storage: localStorage,
key: 'app-user-state',
}
})
// 自定义存储后端(如加密存储)
import { encrypt, decrypt } from '@/utils/crypto'
const encryptedStorage = {
getItem: (key: string) => {
const raw = localStorage.getItem(key)
return raw ? decrypt(raw) : null
},
setItem: (key: string, value: string) => {
localStorage.setItem(key, encrypt(value))
},
removeItem: (key: string) => {
localStorage.removeItem(key)
}
}
// 使用自定义存储
export const useSettingsStore = defineStore('settings', () => {
const apiKey = ref('')
const apiEndpoint = ref('')
return { apiKey, apiEndpoint }
}, {
persist: {
pick: ['apiKey', 'apiEndpoint'],
storage: encryptedStorage,
}
})
TypeScript类型安全与跨组件状态共享
Pinia的TypeScript集成是相比Vuex的显著优势。Setup Store中所有state和getters的类型自动推导,无需手动声明接口。组件中使用Store时,$state、$patch等API也有完整类型提示。
// 组件中使用Store
import { defineComponent } from 'vue'
import { useUserStore } from '@/stores/user'
import { useCartStore } from '@/stores/cart'
import { storeToRefs } from 'pinia'
export default defineComponent({
setup() {
const userStore = useUserStore()
const cartStore = useCartStore()
// storeToRefs 解构保持响应性
// 直接解构 state/getters 会丢失响应性
const { isLoggedIn, displayName, isAdmin } = storeToRefs(userStore)
const { items, totalPrice, itemCount } = storeToRefs(cartStore)
// actions 可以直接解构
const { login, logout } = userStore
const { addToCart, removeFromCart } = cartStore
// $patch 批量更新state
function updateUserProfile(data: Partial) {
userStore.$patch((state) => {
if (state.userInfo) {
Object.assign(state.userInfo, data)
}
})
}
// $subscribe 监听state变化
cartStore.$subscribe((mutation, state) => {
console.log('cart changed:', mutation.type, state.items.length)
}, { detached: true }) // detached: 组件卸载后仍监听
// $onAction 监听action调用
userStore.$onAction(({ name, after, onError }) => {
console.log(`action ${name} started`)
after((result) => {
console.log(`action ${name} completed`, result)
})
onError((error) => {
console.error(`action ${name} failed`, error)
})
})
return {
isLoggedIn, displayName, isAdmin,
items, totalPrice, itemCount,
login, logout, addToCart, removeFromCart,
updateUserProfile,
}
}
})
$subscribe和$onAction为状态调试和副作用管理提供了细粒度控制。$subscribe的detached选项允许在组件销毁后继续监听Store变化,适用于全局数据同步场景(如WebSocket推送更新Store后触发其他模块响应)。$onAction可用于统一记录用户操作日志或实现action级别的乐观更新回滚。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3pinia-zhuang-tai-guan-li-shi-zhan-store-mo-kuai-hua-she/