阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > Pinia状态管理

Pinia状态管理

作者:陈川 阅读数:44467人阅读 分类: Vue.js

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 的最佳实践:

  1. 避免在 getters 中进行昂贵的计算
  2. 使用 $patch 进行批量更新
  3. 合理分割 store,避免单个 store 过于庞大
  4. 使用 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 有很好的集成,可以方便地调试和跟踪状态变化。

  1. 在 DevTools 中查看所有注册的 store
  2. 跟踪 state 的变化
  3. 查看 getters 的缓存状态
  4. 手动触发 actions 进行测试

迁移从 Vuex 到 Pinia

对于现有的 Vuex 项目,可以逐步迁移到 Pinia:

  1. 首先安装 Pinia 并创建基本配置
  2. 选择一个简单的 store 开始迁移
  3. 逐步将 Vuex 的 state 转换为 Pinia 的 state
  4. 将 mutations 转换为直接 state 修改或 actions
  5. 将 Vuex 的 getters 转换为 Pinia 的 getters
  6. 更新组件以使用新的 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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌