Pinia 状态管理

引言

在 Vue 3 项目中,状态管理是不可或缺的一部分。随着 Vuex 逐渐被 Pinia 取代,Pinia 凭借其更简洁的 API、更好的 TypeScript 支持和更轻量的体积,成为了 Vue 生态中的首选状态管理方案。本文将深入讲解 Pinia 的核心概念和实战技巧。

什么是 Pinia?

Pinia 是 Vue 官方推荐的状态管理库,可以看作是 Vuex 的继任者。它具有以下特点:

  • 轻量级:仅 1KB 大小(压缩后)
  • TypeScript 友好:完整的类型推断
  • 简洁 API:基于 Composition API 的设计
  • 模块化:天然支持代码分割
  • DevTools 支持:集成 Vue DevTools

核心概念

Store

Store 是 Pinia 的核心,用于存储和管理应用状态。每个 Store 都是独立的,可以单独使用。

State

State 是响应式的数据源,类似于 Vue 组件中的 data

Getters

Getters 用于计算派生状态,类似于 Vue 的 computed

Actions

Actions 用于处理业务逻辑,可以包含异步操作,类似于 Vue 组件中的 methods

快速上手

安装

复制代码
npm install pinia

创建 Store

使用 defineStore 创建 Store:

javascript 复制代码
// stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  // State
  state: () => ({
    count: 0,
    name: 'Pinia'
  }),
  
  // Getters
  getters: {
    doubleCount: (state) => state.count * 2,
    fullName: (state) => `Hello, ${state.name}!`
  },
  
  // Actions
  actions: {
    increment() {
      this.count++
    },
    decrement() {
      this.count--
    },
    setCount(value) {
      this.count = value
    },
    async fetchCount() {
      const response = await fetch('/api/count')
      const data = await response.json()
      this.count = data.count
    }
  }
})

在组件中使用

xml 复制代码
<template>
  <div>
    <h1>{{ counterStore.fullName }}</h1>
    <p>Count: {{ counterStore.count }}</p>
    <p>Double: {{ counterStore.doubleCount }}</p>
    <button @click="counterStore.increment">+</button>
    <button @click="counterStore.decrement">-</button>
  </div>
</template>

<script setup>
import { useCounterStore } from '@/stores/counter'

const counterStore = useCounterStore()
</script>

高级用法

Store 持久化

使用 pinia-plugin-persistedstate 实现状态持久化:

复制代码
npm install pinia-plugin-persistedstate
javascript 复制代码
// main.js
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

app.use(pinia)
javascript 复制代码
// stores/user.js
export const useUserStore = defineStore('user', {
  state: () => ({
    token: '',
    userInfo: null
  }),
  actions: {
    login(token, userInfo) {
      this.token = token
      this.userInfo = userInfo
    }
  },
  persist: true // 自动持久化到 localStorage
})

多个 Store 协作

javascript 复制代码
// stores/cart.js
import { defineStore } from 'pinia'
import { useProductStore } from './product'

export const useCartStore = defineStore('cart', {
  state: () => ({
    items: []
  }),
  actions: {
    addToProduct(productId, quantity) {
      const productStore = useProductStore()
      const product = productStore.getProductById(productId)
      
      const existingItem = this.items.find(item => item.id === productId)
      if (existingItem) {
        existingItem.quantity += quantity
      } else {
        this.items.push({
          id: productId,
          name: product.name,
          price: product.price,
          quantity
        })
      }
    },
    
    get totalPrice() {
      return this.items.reduce((sum, item) => {
        return sum + item.price * item.quantity
      }, 0)
    }
  }
})

组合 Store

javascript 复制代码
// stores/composed.js
import { defineStore } from 'pinia'
import { useUserStore } from './user'
import { useCartStore } from './cart'

export const useCheckoutStore = defineStore('checkout', () => {
  const userStore = useUserStore()
  const cartStore = useCartStore()
  
  const canCheckout = computed(() => {
    return userStore.isLoggedIn && cartStore.items.length > 0
  })
  
  const checkout = async () => {
    if (!canCheckout.value) {
      throw new Error('Cannot checkout')
    }
    
    // 处理结账逻辑
    const result = await fetch('/api/checkout', {
      method: 'POST',
      body: JSON.stringify({
        userId: userStore.userInfo.id,
        items: cartStore.items
      })
    })
    
    return result.json()
  }
  
  return { canCheckout, checkout }
})

最佳实践

1. 合理的 Store 划分

按业务模块划分 Store,而不是按功能类型:

bash 复制代码
stores/
├── user.js      # 用户相关
├── product.js   # 商品相关
├── cart.js      # 购物车
└── order.js     # 订单

2. 使用 TypeScript

typescript 复制代码
interface User {
  id: number
  name: string
  email: string
}

export const useUserStore = defineStore('user', {
  state: (): { user: User | null } => ({
    user: null
  }),
  actions: {
    setUser(user: User) {
      this.user = user
    }
  }
})

3. 避免直接修改 State

始终通过 Actions 修改状态:

scss 复制代码
// ❌ 不推荐
store.count = 100

// ✅ 推荐
store.setCount(100)

4. 使用 Getters 进行派生计算

javascript 复制代码
// ✅ 推荐
getters: {
  activeUsers: (state) => state.users.filter(user => user.isActive)
}

// ❌ 不推荐
computed: {
  activeUsers() {
    return this.users.filter(user => user.isActive)
  }
}

总结

Pinia 以其简洁的 API、优秀的 TypeScript 支持和轻量的体积,成为了 Vue 3 项目状态管理的首选。通过合理的 Store 划分、类型安全和最佳实践,可以构建出可维护、可扩展的状态管理方案。

关键要点:

  • 使用 defineStore 创建 Store
  • State、Getters、Actions 三大核心
  • 支持持久化和多 Store 协作
  • 推荐按业务模块划分 Store
  • 充分利用 TypeScript 类型推断

Pinia 让 Vue 应用的状态管理变得更加简单和优雅!

相关推荐
不减20斤不改头像1 小时前
手机一句话开发贪吃蛇!TRAE SOLO 移动端 AI 编程实测
前端·后端
xuankuxiaoyao2 小时前
Vue.js实践-组件基础下
前端·javascript·vue.js
一棵白菜2 小时前
Claude Code + Amazon Bedrock 使用指南
前端
大家的林语冰3 小时前
前端周刊:axios 疑遭朝鲜黑客“钓鱼“;CSS 新函数上线;npm 上线深色主题;Oxlint 兼容表;ESLint 支持 Temporal......
前端·javascript·css
哀木4 小时前
一个简单的套壳方案,就能让你的 Agent 少做重复初始化
前端
问心无愧05134 小时前
ctf show web入门27
前端
小村儿4 小时前
给 AI Agent 装上"长期记忆":Karpathy 的 LLM Wiki 思想,我做成了工具
前端·后端·ai编程
竹林8184 小时前
用ethers.js连接MetaMask实现Web3钱包登录:从踩坑到稳定运行的完整记录
前端·javascript
heyCHEEMS4 小时前
如何用 Recast 实现静态配置文件源码级读写
前端·node.js