Pinia 状态管理:从入门到精通

一、Pinia 简介

1.1 什么是 Pinia?

Pinia 是 Vue 官方推荐的状态管理库,是 Vuex 的继任者。它简化了状态管理逻辑,原生支持 TypeScript,且与 Vue3 的 Composition API 完美契合。本文将从基础用法到高级技巧,全面讲解 Pinia 的使用。

1.2 为什么选择 Pinia?

  • 类型安全:完美的 TypeScript 支持
  • 开发工具:集成 Vue DevTools
  • 模块化:不需要嵌套模块
  • 轻量级:体积小巧,约 1KB
  • 组合式 API:完美支持 Vue3 组合式 API
  • 零依赖:不依赖其他库

二、快速开始

2.1 安装

npm install pinia
# 或
yarn add pinia
# 或
pnpm add pinia

2.2 基本配置

// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const app = createApp(App)
const pinia = createPinia()

app.use(pinia)
app.mount('#app')

三、核心概念

3.1 定义 Store

选项式风格
// stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  // 状态
  state: () => ({
    count: 0,
    name: '计数器'
  }),
  
  // 计算属性
  getters: {
    doubleCount: (state) => state.count * 2,
    doubleCountPlusOne() {
      return this.doubleCount + 1
    }
  },
  
  // 操作方法
  actions: {
    increment() {
      this.count++
    },
    async incrementAsync() {
      await new Promise(resolve => setTimeout(resolve, 1000))
      this.increment()
    }
  }
})
组合式风格
// stores/user.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useUserStore = defineStore('user', () => {
  // 状态
  const user = ref(null)
  const token = ref('')
  
  // 计算属性
  const isLoggedIn = computed(() => !!token.value)
  
  // 操作方法
  function login(userData) {
    user.value = userData
    token.value = 'token'
  }
  
  function logout() {
    user.value = null
    token.value = ''
  }
  
  async function fetchUserProfile() {
    const response = await fetch('/api/user')
    user.value = await response.json()
  }
  
  return {
    user,
    token,
    isLoggedIn,
    login,
    logout,
    fetchUserProfile
  }
})

3.2 在组件中使用 Store

<template>
  <div>
    <h2>{{ counterStore.name }}</h2>
    <p>计数: {{ counterStore.count }}</p>
    <p>双倍计数: {{ counterStore.doubleCount }}</p>
    <p>双倍计数+1: {{ counterStore.doubleCountPlusOne }}</p>
    
    <button @click="counterStore.increment()">+1</button>
    <button @click="counterStore.incrementAsync()">异步+1</button>
    <button @click="reset">重置</button>
    
    <div v-if="userStore.isLoggedIn">
      欢迎, {{ userStore.user?.name }}
      <button @click="userStore.logout()">退出</button>
    </div>
  </div>
</template>

<script setup>
import { useCounterStore } from '@/stores/counter'
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'

// 使用 store
const counterStore = useCounterStore()
const userStore = useUserStore()

// 方法可以直接使用
const reset = () => {
  counterStore.$reset() // 重置到初始状态
}

// 使用 storeToRefs 保持响应式
const { count, doubleCount } = storeToRefs(counterStore)
</script>

四、高级特性

4.1 状态持久化

// stores/persisted-store.js
import { defineStore } from 'pinia'
import { ref, watch } from 'vue'

export const usePersistedStore = defineStore('persisted', () => {
  const state = ref(JSON.parse(localStorage.getItem('app-state') || '{}'))
  
  // 监听状态变化并持久化
  watch(
    state,
    (newState) => {
      localStorage.setItem('app-state', JSON.stringify(newState))
    },
    { deep: true }
  )
  
  function updateState(key, value) {
    state.value[key] = value
  }
  
  return {
    state,
    updateState
  }
})

4.2 插件系统

// plugins/persistence.js
export const persistencePlugin = ({ store }) => {
  // 从 localStorage 恢复状态
  const stored = localStorage.getItem(`pinia-${store.$id}`)
  if (stored) {
    store.$patch(JSON.parse(stored))
  }
  
  // 监听变化并保存
  store.$subscribe((mutation, state) => {
    localStorage.setItem(`pinia-${store.$id}`, JSON.stringify(state))
  })
}

// main.js
import { createPinia } from 'pinia'
import { persistencePlugin } from './plugins/persistence'

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

4.3 Store 间通信

// stores/cart.js
import { defineStore } from 'pinia'

export const useCartStore = defineStore('cart', {
  state: () => ({
    items: []
  }),
  actions: {
    addItem(product) {
      this.items.push(product)
    }
  }
})

// stores/notification.js
import { defineStore } from 'pinia'

export const useNotificationStore = defineStore('notification', {
  state: () => ({
    messages: []
  }),
  actions: {
    showMessage(message) {
      this.messages.push(message)
      setTimeout(() => {
        this.messages.shift()
      }, 3000)
    }
  }
})

// 在组件中使用
import { useCartStore } from '@/stores/cart'
import { useNotificationStore } from '@/stores/notification'

const cartStore = useCartStore()
const notificationStore = useNotificationStore()

const addToCart = (product) => {
  cartStore.addItem(product)
  notificationStore.showMessage('商品已添加到购物车')
}

五、最佳实践

5.1 项目结构

src/
├── stores/
│   ├── index.js          # Store 导出文件
│   ├── app.js           # 应用级别状态
│   ├── auth.js          # 认证状态
│   ├── user.js          # 用户数据
│   ├── products.js      # 商品数据
│   └── cart.js          # 购物车
└── components/
    └── ...

5.2 Store 导出文件

// stores/index.js
export { useAppStore } from './app'
export { useAuthStore } from './auth'
export { useUserStore } from './user'
export { useProductsStore } from './products'
export { useCartStore } from './cart'

// 在组件中导入
import { useAuthStore, useUserStore } from '@/stores'

5.3 类型安全(TypeScript)

// stores/types.ts
export interface User {
  id: number
  name: string
  email: string
  role: 'admin' | 'user'
}

export interface Product {
  id: number
  name: string
  price: number
  category: string
}

// stores/user.ts
import { defineStore } from 'pinia'
import type { User } from './types'

interface UserState {
  user: User | null
  token: string | null
  preferences: {
    theme: 'light' | 'dark'
    language: string
  }
}

export const useUserStore = defineStore('user', {
  state: (): UserState => ({
    user: null,
    token: null,
    preferences: {
      theme: 'light',
      language: 'zh-CN'
    }
  }),
  
  getters: {
    isLoggedIn: (state) => !!state.token,
    userName: (state) => state.user?.name || 'Guest'
  },
  
  actions: {
    setUser(user: User) {
      this.user = user
    },
    
    updatePreferences(preferences: Partial<UserState['preferences']>) {
      this.preferences = { ...this.preferences, ...preferences }
    }
  }
})

5.4 测试 Store

// __tests__/counter.store.spec.js
import { setActivePinia, createPinia } from 'pinia'
import { useCounterStore } from '@/stores/counter'

describe('Counter Store', () => {
  beforeEach(() => {
    setActivePinia(createPinia())
  })
  
  it('should increment count', () => {
    const store = useCounterStore()
    expect(store.count).toBe(0)
    
    store.increment()
    expect(store.count).toBe(1)
  })
  
  it('should compute double count', () => {
    const store = useCounterStore()
    store.count = 5
    expect(store.doubleCount).toBe(10)
  })
  
  it('should reset state', () => {
    const store = useCounterStore()
    store.count = 10
    store.$reset()
    expect(store.count).toBe(0)
  })
})

六、性能优化

6.1 选择性响应式

<template>
  <div>
    <!-- 只监听需要的状态 -->
    <p>用户名: {{ userName }}</p>
    <p>主题: {{ theme }}</p>
  </div>
</template>

<script setup>
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'

const userStore = useUserStore()

// 只解构需要的数据,避免不必要的响应式
const { userName, theme } = storeToRefs(userStore)

// 或者直接访问,不创建 ref
const userName = computed(() => userStore.userName)
const theme = computed(() => userStore.preferences.theme)
</script>

6.2 批量更新

// 不好的做法 - 多次更新
userStore.updateName('新名字')
userStore.updateEmail('new@email.com')
userStore.updateTheme('dark')

// 好的做法 - 批量更新
userStore.$patch({
  user: {
    ...userStore.user,
    name: '新名字',
    email: 'new@email.com'
  },
  preferences: {
    ...userStore.preferences,
    theme: 'dark'
  }
})

七、调试技巧

7.1 Vue DevTools

// 在 Store 中添加调试信息
export const useDebugStore = defineStore('debug', {
  state: () => ({
    debug: process.env.NODE_ENV === 'development',
    logs: []
  }),
  
  actions: {
    log(action, payload) {
      if (this.debug) {
        this.logs.push({
          timestamp: new Date(),
          action,
          payload
        })
        console.log(`[Store Log] ${action}:`, payload)
      }
    }
  }
})

7.2 状态快照

// 保存和恢复状态快照
const saveSnapshot = () => {
  const snapshot = pinia.state.value
  localStorage.setItem('pinia-snapshot', JSON.stringify(snapshot))
}

const restoreSnapshot = () => {
  const snapshot = localStorage.getItem('pinia-snapshot')
  if (snapshot) {
    pinia.state.value = JSON.parse(snapshot)
  }
}

八、迁移指南

8.1 从 Vuex 迁移到 Pinia

VuexPinia说明
statestate()基本相同
gettersgetters基本相同
mutationsactionsPinia 没有 mutations
actionsactions支持异步操作
modules多个 store每个模块一个 store

8.2 迁移示例

Vuex:

// vuex store
export default {
  namespaced: true,
  state: {
    count: 0
  },
  getters: {
    doubleCount: state => state.count * 2
  },
  mutations: {
    INCREMENT(state) {
      state.count++
    }
  },
  actions: {
    incrementAsync({ commit }) {
      setTimeout(() => {
        commit('INCREMENT')
      }, 1000)
    }
  }
}

Pinia:

// pinia store
export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0
  }),
  getters: {
    doubleCount: (state) => state.count * 2
  },
  actions: {
    increment() {
      this.count++
    },
    async incrementAsync() {
      await new Promise(resolve => setTimeout(resolve, 1000))
      this.increment()
    }
  }
})

九、总结

Pinia 提供了现代化、类型安全的状态管理解决方案。通过本指南,你应该能够:

  • ✅ 理解 Pinia 的核心概念
  • ✅ 在项目中正确使用 Pinia
  • ✅ 实现复杂的状态管理逻辑
  • ✅ 优化应用性能
  • ✅ 调试和测试 Store
  • ✅ 从 Vuex 平滑迁移

记住,状态管理的目标是让应用的状态变化可预测、易于调试。合理使用 Pinia,可以让你的 Vue3 应用更加健壮和可维护。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

懒羊羊我小弟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值