Pinia 使用指南:Vue 的直观状态管理

118 阅读3分钟

什么是 Pinia?

Pinia 是 Vue.js 的下一代状态管理库,由 Vue.js 核心团队成员开发。它取代了 Vuex,提供了更简洁、直观的 API,同时完美支持 TypeScript。

为什么选择 Pinia?

  • 直观的 API:学习曲线平缓,使用简单
  • TypeScript 支持:完整的类型推断
  • 模块化设计:无需复杂的模块嵌套
  • DevTools 支持:优秀的开发调试体验
  • 轻量级:体积小巧,性能优秀

安装与配置

bash

npm install pinia

在 main.js 中配置:

javascript

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

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

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

核心概念

1. 创建 Store

javascript

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

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    name: 'My Counter'
  }),
  getters: {
    doubleCount: (state) => state.count * 2,
    // 使用其他 getter
    doubleCountPlusOne() {
      return this.doubleCount + 1
    }
  },
  actions: {
    increment() {
      this.count++
    },
    decrement() {
      this.count--
    },
    async incrementAsync() {
      // 支持异步操作
      await new Promise(resolve => setTimeout(resolve, 1000))
      this.increment()
    }
  }
})

2. 组合式 API 风格

Pinia 也支持组合式 API 写法:

javascript

// stores/user.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useUserStore = defineStore('user', () => {
  const user = ref(null)
  const isLoggedIn = computed(() => user.value !== null)
  
  function login(userData) {
    user.value = userData
  }
  
  function logout() {
    user.value = null
  }
  
  return {
    user,
    isLoggedIn,
    login,
    logout
  }
})

在组件中使用

基本使用

vue

<template>
  <div>
    <h1>{{ counterStore.name }}</h1>
    <p>Count: {{ counterStore.count }}</p>
    <p>Double: {{ counterStore.doubleCount }}</p>
    
    <button @click="counterStore.increment()">+</button>
    <button @click="counterStore.decrement()">-</button>
    <button @click="counterStore.incrementAsync()">Async +</button>
    
    <!-- 使用解构保持响应式 -->
    <p>解构后的 Count: {{ count }}</p>
  </div>
</template>

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

const counterStore = useCounterStore()

// 正确解构方式,保持响应式
const { count, name } = storeToRefs(counterStore)

// 直接使用 action
const { increment } = counterStore
</script>

状态修改

javascript

// 方式一:直接修改
counterStore.count++

// 方式二:使用 $patch(批量更新)
counterStore.$patch({
  count: counterStore.count + 1,
  name: 'New Name'
})

// 方式三:使用 $patch 函数
counterStore.$patch((state) => {
  state.count += 1
  state.name = 'New Name'
})

// 方式四:使用 action
counterStore.increment()

// 重置状态
counterStore.$reset()

// 订阅状态变化
counterStore.$subscribe((mutation, state) => {
  console.log('状态发生变化:', mutation, state)
})

高级用法

1. 数据持久化

javascript

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

// 在 main.js 中使用
pinia.use(piniaPersistence)

2. Store 间通信

javascript

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

export const useCartStore = defineStore('cart', {
  state: () => ({
    items: []
  }),
  actions: {
    addItem(item) {
      const userStore = useUserStore()
      
      if (!userStore.isLoggedIn) {
        throw new Error('请先登录')
      }
      
      this.items.push(item)
    }
  }
})

3. 服务端渲染 (SSR)

javascript

// 在 setup 函数中
export default {
  async setup() {
    const store = useStore()
    
    // 在服务端预取数据
    if (import.meta.env.SSR) {
      await store.fetchData()
    }
    
    return { store }
  }
}

最佳实践

1. 组织 Store 结构

text

stores/
├── index.js          # 导出所有 store
├── user.js          # 用户相关状态
├── cart.js          # 购物车状态
├── products.js      # 商品状态
└── ui.js           # UI 状态

2. 使用 TypeScript

typescript

// stores/counter.ts
import { defineStore } from 'pinia'

interface CounterState {
  count: number
  name: string
}

export const useCounterStore = defineStore('counter', {
  state: (): CounterState => ({
    count: 0,
    name: 'My Counter'
  }),
  getters: {
    doubleCount(state): number {
      return state.count * 2
    }
  },
  actions: {
    increment(): void {
      this.count++
    }
  }
})

3. 测试 Store

javascript

import { setActivePinia, createPinia } from 'pinia'
import { useCounterStore } from '@/stores/counter'

describe('Counter Store', () => {
  beforeEach(() => {
    setActivePinia(createPinia())
  })

  it('should increment count', () => {
    const store = useCounterStore()
    store.increment()
    expect(store.count).toBe(1)
  })
})

与 Vuex 的对比

特性VuexPinia
API 复杂度较高较低
TypeScript 支持需要额外配置原生支持
模块系统嵌套模块扁平结构
体积较大较小
学习曲线较陡平缓

总结

Pinia 为 Vue 应用提供了现代化、直观的状态管理解决方案。其简洁的 API 设计、优秀的 TypeScript 支持以及出色的开发体验,使其成为新项目的首选状态管理库。

无论是小型项目还是大型企业级应用,Pinia 都能提供良好的开发体验和可维护性。建议在新项目中直接使用 Pinia,对于现有使用 Vuex 的项目,可以根据实际情况逐步迁移。