Pinia 状态管理:Vue 3 的官方存储库

FreeGuideOnline 最新 2026-06-15

Pinia 状态管理完全指南

什么是 Pinia?

Pinia 是 Vue 生态系统中新一代的状态管理库,被官方指定为 Vue 3 的默认状态管理方案。它重新思考了 Vuex 的设计理念,以更简洁的 API、完整的 TypeScript 支持和更灵活的使用方式,让状态管理变得前所未有的轻松。

与 Vuex 相比,Pinia 拥有以下核心优势:

  • 极致简洁:移除了 mutations,直接通过 actions 修改状态。
  • 完整的 TypeScript 推断:无需自定义复杂的包装类型,享受开箱即用的类型安全。
  • 扁平化架构:不再有模块嵌套,每个 store 都是独立的。
  • 真正的代码分割:stores 可以在需要时自动注册。
  • Devtools 支持:无缝集成 Vue Devtools,支持时间旅行调试。

快速上手

安装 Pinia

在已有的 Vue 3 项目中,使用你喜欢的包管理器安装 Pinia:

npm install pinia
# 或者
yarn add pinia

在 Vue 应用中注册

打开入口文件(通常是 main.jsmain.ts),创建 Pinia 实例并传递给应用:

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')

至此,Pinia 就已经准备就绪,你可以在任何组件中使用它了。

创建你的第一个 Store

Store 是 Pinia 的核心概念,它就像一块独立的数据孤岛,承载着状态和业务逻辑。使用 defineStore() 函数来定义,第一个参数是 store 的唯一 ID(名称)。

定义一个基础 Store

我们创建一个管理用户信息的 store,文件命名为 stores/user.js

import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    name: '张三',
    age: 25,
    isLoggedIn: false
  }),
  getters: {
    // 类似于计算属性,自动追踪依赖
    greeting: (state) => `你好,我是 ${state.name},今年 ${state.age} 岁`,
    // 使用 this 访问其他 getter
    adultLabel: function () {
      return this.age >= 18 ? '成年' : '未成年'
    }
  },
  actions: {
    // 可以同步或异步修改状态
    login(name) {
      this.name = name
      this.isLoggedIn = true
    },
    updateAge(age) {
      this.age = age
    },
    async fetchUserData(id) {
      try {
        const response = await fetch(`/api/users/${id}`)
        const data = await response.json()
        this.name = data.name
        this.age = data.age
        // 直接通过 this 访问 state 和 actions
      } catch (error) {
        console.error('获取用户数据失败', error)
      }
    }
  }
})

在组件中使用

在任意 Vue 组件中,调用 use 函数返回 store 实例,就可以愉快地使用了:

<template>
  <div>
    <p>{{ userStore.greeting }}</p>
    <p v-if="userStore.isLoggedIn">已登录:{{ userStore.name }}</p>
    <button @click="handleLogin">登录</button>
  </div>
</template>

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

const userStore = useUserStore()

function handleLogin() {
  userStore.login('李四')
}
</script>

状态 (State)

State 是 store 的响应式数据中心,使用箭头函数返回对象的方式定义,以确保每个 store 实例相互独立。

读取和写入状态

  • 直接通过 store 实例访问:userStore.name
  • 修改状态可以直接赋值:userStore.name = '王五'
  • 一次性修改多个属性,推荐使用 $patch 方法:
userStore.$patch({
  name: '赵六',
  age: 30,
  isLoggedIn: true
})
  • 或者传递函数版本的 $patch,进行复杂变更:
userStore.$patch((state) => {
  state.name = '孙七'
  state.age += 1
  // 甚至可以执行逻辑
})

重置状态

Pinia 提供了 $reset() 方法,一键将 state 恢复到初始定义时的值:

userStore.$reset()

替换整个状态

通过 $state 属性,可以用新对象完全替换当前状态(通常用于 SSR 场景同步状态):

userStore.$state = { name: '新用户', age: 0, isLoggedIn: false }

派生数据 (Getters)

Getters 完全等同于 Vue 组件中的计算属性,用来基于 state 派生出新的数据。

定义与使用

Getters 通过 state 参数访问状态,也支持通过 this 访问整个 store 实例(但注意不要使用箭头函数,否则会丢失正确的 this 上下文)。

getters: {
  // 接收 state 参数
  fullInfo(state) {
    return `姓名: ${state.name}, 年龄: ${state.age}`
  },
  // 使用 this 访问其他 getter
  welcomeMessage() {
    return this.isLoggedIn ? `欢迎回来,${this.name}` : '请先登录'
  }
}

在组件或 actions 中,就像访问普通属性一样使用 getter:

console.log(userStore.fullInfo)

向 Getters 传递参数

Getters 本身不接受额外参数,但可以通过返回一个函数来实现:这样在访问时就可以传入参数了。

getters: {
  getUserById: (state) => {
    return (userId) => state.users.find(user => user.id === userId)
  }
}

// 使用
const user = userStore.getUserById(1)

动作 (Actions)

Actions 是 store 中定义方法的地方,它们可以直接通过 this 访问整个 store 实例,并可以包含任何同步或异步操作。不再有 mutations,actions 就是修改状态的唯一入口。

同步 Action

actions: {
  increment() {
    this.count++
  },
  changeName(newName) {
    this.name = newName
  }
}

异步 Action

Actions 可以是异步的,使用 async/await 或返回 Promise。这对处理 API 调用非常方便。

actions: {
  async login(credentials) {
    this.loading = true
    try {
      const response = await authApi.login(credentials)
      this.user = response.data
      this.isLoggedIn = true
    } catch (error) {
      this.errorMessage = error.message
    } finally {
      this.loading = false
    }
  }
}

调用其他 Store 的 Action

在 Action 中,我们可以通过获取其他 store 实例来协作:

import { useCartStore } from './cart'

actions: {
  async purchase() {
    const cartStore = useCartStore()
    if (cartStore.items.length === 0) return

    await api.placeOrder(this.orderInfo)
    cartStore.clearCart()
  }
}

组合式 Store (Setup Stores)

Pinia 提供了第二种定义 store 的方式——使用类似 Vue 组合式 API 的语法。这种方式更加灵活,可以利用 Vue 的 refreactivecomputed 甚至其他组合式函数。

import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useCounterStore = defineStore('counter', () => {
  // state
  const count = ref(0)
  const name = ref('计数器')

  // getters 使用 computed
  const doubleCount = computed(() => count.value * 2)

  // actions 就是普通函数
  function increment() {
    count.value++
  }

  function reset() {
    count.value = 0
  }

  // 暴露出去的属性和方法
  return { count, name, doubleCount, increment, reset }
})

选项式 Store vs 组合式 Store

  • 选项式 Store 更接近 Vuex 的习惯,结构清晰,适合团队协作。
  • 组合式 Store 更加灵活,可以充分复用组合式函数,适合喜欢 Composition API 的开发者。
  • 两者在功能和性能上完全等价,你可以根据喜好混用。

在组件外部使用 Store

Pinia store 并不局限于组件,在任何 JavaScript 代码中都可以调用。这得益于 Pinia 的 “active pinia” 机制。只要确保在调用 useStore() 时 Pinia 已经被安装即可。

// 在路由守卫中
import { createRouter } from 'vue-router'
import { useUserStore } from '@/stores/user'

const router = createRouter({ ... })

router.beforeEach((to) => {
  const userStore = useUserStore()
  if (!userStore.isLoggedIn && to.meta.requiresAuth) {
    return '/login'
  }
})

如果你需要在 Pinia 安装之前就获取 store(例如在 setup 外的初始化代码中),则必须手动传入 pinia 实例:

import { createPinia } from 'pinia'
import { useUserStore } from '@/stores/user'

const pinia = createPinia()
const userStore = useUserStore(pinia) // 明确传入 pinia 实例

常用辅助函数与插件

storeToRefs()

当你需要从 store 中解构 state 和 getters 时,如果直接使用 ES6 解构会丢失响应性。storeToRefs() 可以安全地将它们转化为响应式引用。

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

const userStore = useUserStore()
const { name, age, isLoggedIn, greeting } = storeToRefs(userStore)
// name 现在是一个 ref,可以在模板中直接使用 {{ name }}
// 但 actions 不能解构,需保留 userStore.login
</script>

$subscribe()

监听 store 的变化,类似于 Vue 的 watch。会在每次 state 被修改后触发。

userStore.$subscribe((mutation, state) => {
  // mutation.type: 'direct' | 'patch object' | 'patch function'
  console.log('变化类型:', mutation.type)
  console.log('新状态:', state)
  // 存储到 localStorage 等
  localStorage.setItem('user', JSON.stringify(state))
}, { detached: false }) // detached: true 可以在组件销毁后依然存活

$onAction()

监听 action 的调用和结果,支持在 action 执行前、后或出错时执行回调。非常适合全局的日志记录、性能监控等。

userStore.$onAction(({
  name, // action 名称
  store, // store 实例
  args, // 调用参数数组
  after, // 完成后的回调
  onError, // 错误时的回调
}) => {
  const startTime = Date.now()

  console.log(`Action "${name}" 开始执行,参数:`, args)

  after((result) => {
    console.log(`Action "${name}" 执行成功,耗时: ${Date.now() - startTime}ms`)
  })

  onError((error) => {
    console.error(`Action "${name}" 执行失败`, error)
  })
})

使用插件扩展 Pinia

Pinia 的插件机制允许你为每个 store 添加全局属性或功能。比如添加一个通用的 $log 方法,或者持久化插件。

import { createPinia } from 'pinia'

const pinia = createPinia()

// 自定义插件
pinia.use(({ store }) => {
  // 为所有 store 添加一个调试属性
  store.$state.debug = false

  // 添加一个全局方法
  store.$log = (message) => {
    console.log(`[${store.$id}]`, message)
  }
})

社区中流行的持久化插件 pinia-plugin-persistedstate 可以轻松实现状态持久化到 localStorage 或 sessionStorage。

实际项目最佳实践

  • 按功能拆分 stores:不要创建一个大而全的 store,为每个功能模块创建独立的 store,如 useUserStoreuseCartStoreuseOrderStore
  • 保持 action 的纯粹:action 应该包含业务逻辑,而不要直接操作 UI。更改 UI 状态应通过修改 state,由组件响应。
  • 合理使用 getter 而非重复计算:避免在多个组件中编写相同的派生逻辑,集中到 getter 中。
  • 类型安全:如果你使用 TypeScript,Pinia 会自动推断所有类型,几乎不用额外编写接口。对于复杂 state,辅助类型推断可以使用 interface 定义状态结构。
  • 使用组合式 Store 时注意一致性:在团队中统一风格,避免选项式和组合式混用导致维护困难。

小结

Pinia 以极简的 API、天生对 TypeScript 的友好、灵活的架构,彻底改变了 Vue 状态管理的体验。无论是小型项目还是大型企业应用,Pinia 都能提供优雅且高效的状态管理方案。现在就开始在项目中使用 Pinia,告别繁琐的样板代码,享受清晰直观的数据流吧。