跳至内容

Pinia 状态管理

Pinia 是 Vue 官方推荐的状态管理库(取代 Vuex),专为 Vue 3 设计,具有更简洁的 API、完整的 TypeScript 支持和开发工具集成。核心概念只有三个:State(状态)、Getters(计算值)、Actions(操作)

安装与配置

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

const app = createApp(App)
app.use(createPinia())  // 必须在 use(router) 之前注册
app.mount('#app')

定义 Store

Pinia 推荐使用组合式 Store(类似 <script setup>),也支持选项式 Store。

组合式 Store(推荐)

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

export const useCounterStore = defineStore('counter', () => {
  // state:响应式变量
  const count = ref(0)
  const name = ref('Eduardo')

  // getters:计算属性
  const doubleCount = computed(() => count.value * 2)

  // actions:修改 state 的方法(可异步)
  function increment() {
    count.value++
  }

  function reset() {
    count.value = 0
  }

  async function fetchInitialCount() {
    const res = await fetch('/api/count')
    const data = await res.json()
    count.value = data.count
  }

  return { count, name, doubleCount, increment, reset, fetchInitialCount }
})

选项式 Store

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

export const useUserStore = defineStore('user', {
  state: () => ({
    currentUser: null,
    token: '',
  }),

  getters: {
    isLoggedIn: (state) => !!state.token,
    username: (state) => state.currentUser?.name ?? '游客',
  },

  actions: {
    async login(credentials) {
      const res = await fetch('/api/login', {
        method: 'POST',
        body: JSON.stringify(credentials),
      })
      const data = await res.json()
      this.token = data.token
      this.currentUser = data.user
    },

    logout() {
      this.token = ''
      this.currentUser = null
    },
  },
})

在组件中使用

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

const counterStore = useCounterStore()
const userStore = useUserStore()

// 直接解构会失去响应性!使用 storeToRefs 保持响应性
const { count, doubleCount } = storeToRefs(counterStore)
const { isLoggedIn, username } = storeToRefs(userStore)

// actions 直接解构(函数不需要响应性)
const { increment, reset } = counterStore
</script>

<template>
  <div>
    <p>计数{{ count }}双倍{{ doubleCount }}</p>
    <button @click="increment">+1</button>
    <button @click="reset">重置</button>

    <p v-if="isLoggedIn">欢迎{{ username }}</p>
    <button v-else @click="userStore.login({ name: 'Alice', pwd: '123' })">
      登录
    </button>
  </div>
</template>

直接修改 State

const store = useCounterStore()

// 方式一:直接赋值(选项式 Store 在 action 外,或组合式 Store)
store.count++

// 方式二:$patch 批量修改(性能更好,只触发一次渲染)
store.$patch({
  count: store.count + 1,
  name: 'new name',
})

// 方式三:$patch 函数形式(适合数组操作)
store.$patch((state) => {
  state.items.push({ id: Date.now(), name: '新项目' })
  state.count++
})

// 重置为初始 state
store.$reset()

// 替换整个 state
store.$state = { count: 10, name: 'replaced' }

订阅 Store 变化

const store = useCounterStore()

// 订阅 state 变化(类似 Vuex 的 subscribe)
const unsubscribe = store.$subscribe((mutation, state) => {
  console.log(mutation.type)    // 'direct' | 'patch object' | 'patch function'
  console.log(mutation.storeId) // 'counter'
  // 可用于持久化到 localStorage
  localStorage.setItem('counter', JSON.stringify(state))
})

// 取消订阅
unsubscribe()

// 订阅 action
store.$onAction(({ name, args, after, onError }) => {
  console.log(`action "${name}" 被调用,参数:`, args)
  after(result => console.log('action 完成,结果:', result))
  onError(error => console.error('action 出错:', error))
})

Store 间调用

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

export const useCartStore = defineStore('cart', {
  actions: {
    async checkout() {
      const userStore = useUserStore()
      if (!userStore.isLoggedIn) {
        throw new Error('请先登录')
      }
      // 结算逻辑...
    }
  }
})

持久化插件(pinia-plugin-persistedstate)

npm install pinia-plugin-persistedstate
// main.js
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
// 在 Store 中开启持久化
export const useAuthStore = defineStore('auth', {
  state: () => ({ token: '', user: null }),
  persist: true,  // 默认持久化全部 state 到 localStorage
})

// 细粒度配置
export const useSettingsStore = defineStore('settings', {
  state: () => ({ theme: 'light', language: 'zh' }),
  persist: {
    key: 'app-settings',       // 自定义 localStorage key
    storage: sessionStorage,   // 改用 sessionStorage
    pick: ['theme'],           // 只持久化 theme(不持久化 language)
  },
})

与 Vue Router 集成

// router/index.js — 在守卫中使用 Store
import { createRouter, createWebHistory } from 'vue-router'
import { useAuthStore } from '@/stores/auth'

const router = createRouter({ ... })

router.beforeEach((to) => {
  // 注意:必须在 router.beforeEach 的回调内部调用 useStore
  const auth = useAuthStore()

  if (to.meta.requiresAuth && !auth.isLoggedIn) {
    return { name: 'login', query: { redirect: to.fullPath } }
  }
})

export default router
最后更新于