Pinia状态管理
Pinia 的核心概念
Pinia 是 Vue.js 的轻量级状态管理库,它提供了一种简单、直观的方式来管理应用程序的状态。与 Vuex 相比,Pinia 具有更简洁的 API 和更好的 TypeScript 支持。Pinia 的核心概念包括 store、state、getters 和 actions。
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
getters: {
doubleCount: (state) => state.count * 2
},
actions: {
increment() {
this.count++
}
}
})
创建和使用 Store
Pinia 中的 store 是一个包含状态和业务逻辑的实体。要创建一个 store,可以使用 defineStore
函数。每个 store 都有一个唯一的 ID,用于在组件中引用它。
// stores/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
name: 'John Doe',
age: 30,
email: 'john@example.com'
}),
actions: {
updateUser(partialUser) {
this.$patch(partialUser)
}
}
})
在组件中使用 store:
<script setup>
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
</script>
<template>
<div>
<p>Name: {{ userStore.name }}</p>
<p>Age: {{ userStore.age }}</p>
<button @click="userStore.updateUser({ age: 31 })">
Increment Age
</button>
</div>
</template>
State 管理
Pinia 的 state 是 store 的核心部分,它包含了应用程序的数据。state 必须是一个返回初始状态的函数,这确保了每个请求都能获得独立的 store 实例。
export const useProductStore = defineStore('products', {
state: () => ({
items: [
{ id: 1, name: 'Laptop', price: 999 },
{ id: 2, name: 'Phone', price: 699 }
],
loading: false,
error: null
})
})
可以直接修改 state,但更推荐使用 $patch
方法进行批量更新:
// 直接修改
productStore.items.push({ id: 3, name: 'Tablet', price: 399 })
// 使用 $patch
productStore.$patch({
items: [...productStore.items, { id: 3, name: 'Tablet', price: 399 }],
loading: false
})
Getters 的使用
Getters 是 store 的计算属性,它们可以基于 state 派生出新的值。Getters 在内部是缓存的,只有当它们的依赖发生变化时才会重新计算。
export const useCartStore = defineStore('cart', {
state: () => ({
items: []
}),
getters: {
totalItems: (state) => state.items.length,
totalPrice: (state) => state.items.reduce((sum, item) => sum + item.price, 0),
hasItem: (state) => (productId) => state.items.some(item => item.id === productId)
}
})
在组件中使用 getters:
<script setup>
import { useCartStore } from '@/stores/cart'
const cartStore = useCartStore()
</script>
<template>
<div>
<p>Total Items: {{ cartStore.totalItems }}</p>
<p>Total Price: ${{ cartStore.totalPrice }}</p>
<p v-if="cartStore.hasItem(1)">
Product #1 is in your cart
</p>
</div>
</template>
Actions 和异步操作
Actions 相当于组件中的 methods,它们用于封装业务逻辑。Actions 可以是异步的,非常适合处理 API 调用。
export const usePostStore = defineStore('posts', {
state: () => ({
posts: [],
loading: false,
error: null
}),
actions: {
async fetchPosts() {
this.loading = true
this.error = null
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts')
this.posts = await response.json()
} catch (error) {
this.error = error.message
} finally {
this.loading = false
}
},
async createPost(postData) {
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
body: JSON.stringify(postData)
})
this.posts.unshift(await response.json())
}
}
})
在组件中调用 action:
<script setup>
import { usePostStore } from '@/stores/post'
import { onMounted } from 'vue'
const postStore = usePostStore()
onMounted(() => {
postStore.fetchPosts()
})
const handleSubmit = async () => {
await postStore.createPost({
title: 'New Post',
body: 'This is a new post',
userId: 1
})
}
</script>
模块化和组合 Store
Pinia 支持将应用程序的状态分割成多个 store,这些 store 可以相互引用和组合。
// stores/auth.js
export const useAuthStore = defineStore('auth', {
state: () => ({
user: null,
token: null
}),
actions: {
async login(credentials) {
// 登录逻辑
}
}
})
// stores/notification.js
export const useNotificationStore = defineStore('notification', {
state: () => ({
messages: []
}),
actions: {
addMessage(message) {
this.messages.push(message)
}
}
})
在另一个 store 中使用这些 store:
// stores/ui.js
export const useUiStore = defineStore('ui', {
actions: {
async performLogin(credentials) {
const authStore = useAuthStore()
const notificationStore = useNotificationStore()
try {
await authStore.login(credentials)
notificationStore.addMessage('Login successful')
} catch (error) {
notificationStore.addMessage('Login failed')
}
}
}
})
插件和持久化
Pinia 支持插件系统,可以扩展 store 的功能。一个常见的用例是实现状态持久化。
// 创建一个简单的持久化插件
function persistPlugin({ store }) {
const key = `pinia-${store.$id}`
const savedState = localStorage.getItem(key)
if (savedState) {
store.$patch(JSON.parse(savedState))
}
store.$subscribe((mutation, state) => {
localStorage.setItem(key, JSON.stringify(state))
})
}
// 在创建 Pinia 实例时使用插件
import { createPinia } from 'pinia'
const pinia = createPinia()
pinia.use(persistPlugin)
对于更复杂的持久化需求,可以使用 pinia-plugin-persistedstate
插件:
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
// 在 store 中启用持久化
export const useSettingsStore = defineStore('settings', {
state: () => ({
theme: 'light',
language: 'en'
}),
persist: true
})
与 Vue Router 和 Composition API 集成
Pinia 可以很好地与 Vue Router 和 Composition API 集成,创建强大的应用程序架构。
// 在路由守卫中使用 store
import { createRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
const router = createRouter({
// 路由配置
})
router.beforeEach(async (to) => {
const authStore = useAuthStore()
if (to.meta.requiresAuth && !authStore.isAuthenticated) {
return '/login'
}
})
在组合式函数中使用 store:
// composables/useUser.js
import { useUserStore } from '@/stores/user'
import { computed } from 'vue'
export function useUser() {
const userStore = useUserStore()
const fullName = computed(() => {
return `${userStore.firstName} ${userStore.lastName}`
})
async function updateProfile(data) {
await userStore.updateProfile(data)
}
return {
user: userStore.user,
fullName,
updateProfile
}
}
测试 Pinia Store
测试 Pinia store 非常简单,可以使用 Vitest 或 Jest 等测试框架。
import { setActivePinia, createPinia } from 'pinia'
import { useCounterStore } from '@/stores/counter'
import { describe, it, expect, beforeEach } from 'vitest'
describe('Counter Store', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
it('increments count', () => {
const counter = useCounterStore()
expect(counter.count).toBe(0)
counter.increment()
expect(counter.count).toBe(1)
})
it('doubles count', () => {
const counter = useCounterStore()
counter.count = 5
expect(counter.doubleCount).toBe(10)
})
})
性能优化和最佳实践
为了获得最佳性能,可以遵循一些 Pinia 的最佳实践:
- 避免在 getters 中进行昂贵的计算
- 使用
$patch
进行批量更新 - 合理分割 store,避免单个 store 过于庞大
- 使用
storeToRefs
解构 store 时保持响应性
import { storeToRefs } from 'pinia'
import { useProductStore } from '@/stores/product'
const productStore = useProductStore()
// 错误的方式:会失去响应性
const { items, loading } = productStore
// 正确的方式:保持响应性
const { items, loading } = storeToRefs(productStore)
与 Vue DevTools 集成
Pinia 与 Vue DevTools 有很好的集成,可以方便地调试和跟踪状态变化。
- 在 DevTools 中查看所有注册的 store
- 跟踪 state 的变化
- 查看 getters 的缓存状态
- 手动触发 actions 进行测试
迁移从 Vuex 到 Pinia
对于现有的 Vuex 项目,可以逐步迁移到 Pinia:
- 首先安装 Pinia 并创建基本配置
- 选择一个简单的 store 开始迁移
- 逐步将 Vuex 的 state 转换为 Pinia 的 state
- 将 mutations 转换为直接 state 修改或 actions
- 将 Vuex 的 getters 转换为 Pinia 的 getters
- 更新组件以使用新的 Pinia store
// Vuex 示例
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
},
getters: {
doubleCount(state) {
return state.count * 2
}
}
})
// 转换为 Pinia
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
getters: {
doubleCount: (state) => state.count * 2
},
actions: {
increment() {
this.count++
},
incrementAsync() {
setTimeout(() => {
this.increment()
}, 1000)
}
}
})
处理复杂状态关系
对于具有复杂关系的状态,可以使用多个 store 并通过引用 ID 来建立关联。
// stores/authors.js
export const useAuthorStore = defineStore('authors', {
state: () => ({
authors: [
{ id: 1, name: 'Author One' },
{ id: 2, name: 'Author Two' }
]
}),
getters: {
getAuthorById: (state) => (id) => state.authors.find(a => a.id === id)
}
})
// stores/books.js
export const useBookStore = defineStore('books', {
state: () => ({
books: [
{ id: 1, title: 'Book One', authorId: 1 },
{ id: 2, title: 'Book Two', authorId: 2 }
]
}),
getters: {
booksWithAuthors() {
const authorStore = useAuthorStore()
return this.books.map(book => ({
...book,
author: authorStore.getAuthorById(book.authorId)
}))
}
}
})
服务器端渲染 (SSR) 支持
Pinia 完全支持服务器端渲染,可以在 Nuxt.js 等框架中使用。
// 在 Nuxt.js 中使用 Pinia
// nuxt.config.js
export default {
modules: [
'@pinia/nuxt'
]
}
// 然后可以直接在任何组件或页面中使用 store
export default defineComponent({
setup() {
const counter = useCounterStore()
return { counter }
}
})
对于自定义的 SSR 设置,需要确保每个请求都有独立的 store 实例:
// 在服务器入口文件中
import { createPinia } from 'pinia'
export default (context) => {
const pinia = createPinia()
context.app.use(pinia)
}
TypeScript 支持
Pinia 提供了出色的 TypeScript 支持,可以完全类型化 store 的所有部分。
interface UserState {
name: string
age: number
email: string
}
interface UserGetters {
isAdult: boolean
}
interface UserActions {
updateUser: (partialUser: Partial<UserState>) => void
}
export const useUserStore = defineStore<'user', UserState, UserGetters, UserActions>('user', {
state: (): UserState => ({
name: 'John Doe',
age: 30,
email: 'john@example.com'
}),
getters: {
isAdult: (state) => state.age >= 18
},
actions: {
updateUser(partialUser) {
this.$patch(partialUser)
}
}
})
高级模式
Pinia 支持一些高级模式,如动态创建 store 和 store 组合。
// 动态创建 store
function createDynamicStore(id, initialState) {
return defineStore(id, {
state: () => initialState,
// 其他选项
})
}
// 使用
const useDynamicStore = createDynamicStore('dynamic', { value: 0 })
const dynamicStore = useDynamicStore()
// Store 组合
function createCommonStoreFeatures(id) {
return {
id,
state: () => ({
loading: false,
error: null
}),
actions: {
setLoading(loading) {
this.loading = loading
},
setError(error) {
this.error = error
}
}
}
}
export const useEnhancedStore = defineStore('enhanced', {
...createCommonStoreFeatures('enhanced'),
state: () => ({
// 特定状态
data: null
}),
actions: {
async fetchData() {
this.setLoading(true)
try {
// 获取数据
this.data = await fetchData()
} catch (error) {
this.setError(error)
} finally {
this.setLoading(false)
}
}
}
})
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:Vue3项目脚手架
下一篇:VueUse组合式工具库