Vue3 状态管理 Pinia 入门指南

 更新时间:2026年05月24日 09:37:55   作者:叫我少年  
Pinia 是 Vue 3 官方推荐的状态管理库,相较于 Vuex 更加轻量、易于使用,下面就来详细的介绍一下Vue3 状态管理 Pinia 入门,感兴趣的可以了解一下

很多刚接触 Vue3 状态管理的同学都会问:“Vuex 还没学明白,怎么又出了个 Pinia?”这篇就来聊聊 Pinia,看看它到底是什么、为什么要用它、以及怎么用好它。

  1. Pinia 是什么:Pinia 是 Vue 的全新状态管理库,它和 Vuex 有哪些核心区别?
  2. 如何定义 Store:掌握选项式和组合式两种创建 Store 的风格。
  3. 在组件中如何使用:学会在 setup 中正确地使用 Store 并保持响应性。

一、使用场景

Pinia 适用于需要对 Vue 应用进行全局状态管理的场景:

  • 跨组件共享状态:例如用户登录信息、购物车数据、主题配置等
  • 与后端 API 交互:集中管理 API 调用和数据缓存逻辑
  • 调试与开发体验:配合 Vue DevTools 进行状态追踪和时间旅行
  • 类型安全:为 TypeScript 项目提供完整的类型推导支持
  • 模块化管理:将不同领域的全局状态拆分为独立的 Store,易于维护和扩展

二、注意事项

划重点: Pinia 在 Vue 2 和 Vue 3 中均可使用,但本文所有示例均基于 Vue 3 + Script Setup 语法。

  • defineStore的 ID 必须唯一:每个 Store 都需要一个唯一的 ID,用于 DevTools 的连接
  • 没有 mutations​:与 Vuex 不同,Pinia 将状态(state​)与变更逻辑(actions)直接统一,摒弃了繁琐的 mutations
  • 避免过度使用全局状态:组件内部能处理的数据,不要提升到 Store 中

三、基本用法

3.1 安装与配置

yarn add pinia
# 或
npm install pinia

main.ts 中创建 Pinia 实例并注册到 Vue 应用:

// main.ts
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')

3.2 定义 Store

Pinia 提供两种风格来定义 Store:选项式(Options API)组合式(Composition API) 。选择哪个全凭个人喜好,但组合式风格更适合复杂逻辑的复用。

选项式风格:

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

export const useCounterStore = defineStore('counter', {
  // 状态(数据)
  state: () => ({
    count: 0,
    name: '我的计数器',
  }),
  // 计算属性(派生数据)
  getters: {
    // 使用箭头函数时,state 为第一个参数
    doubleCount: (state) => state.count * 2,
    // 需要访问其他 getter 时,使用普通函数
    doubleCountPlusOne(): number {
      return this.doubleCount + 1
    },
  },
  // 操作方法(业务逻辑)
  actions: {
    increment() {
      this.count++
    },
    decrement() {
      this.count--
    },
    // 支持异步操作
    async incrementAsync() {
      await new Promise((resolve) => setTimeout(resolve, 1000))
      this.increment()
    },
    incrementBy(amount: number) {
      this.count += amount
    },
  },
})

组合式风格(推荐):

// stores/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { api } from '@/services/api' // 假设的 API 服务

// 类型定义
interface User {
  id: number
  name: string
  email: string
}
interface LoginCredentials {
  username: string
  password: string
}

export const useUserStore = defineStore('user', () => {
  // 状态(类似 options 的 data)
  const user = ref<User | null>(null)
  const isLoggedIn = ref(false)

  // 计算属性(类似 options 的 getters)
  const userName = computed(() => user.value?.name || '游客')
  const userId = computed(() => user.value?.id || 0)

  // 操作方法(类似 options 的 actions)
  function login(credentials: LoginCredentials) {
    return api.login(credentials).then((response) => {
      user.value = response.user
      isLoggedIn.value = true
      return response
    })
  }

  function logout() {
    user.value = null
    isLoggedIn.value = false
  }

  async function fetchUserProfile() {
    if (!userId.value) return
    try {
      const profile = await api.getUserProfile(userId.value)
      user.value = { ...user.value, ...profile }
    } catch (error) {
      console.error('获取用户资料失败:', error)
    }
  }

  // 返回需要暴露给外部的所有内容
  return { user, isLoggedIn, userName, userId, login, logout, fetchUserProfile }
})

代码解析:

  1. ​​defineStore​:这是定义 Store 的核心函数,第一个参数是 Store 的唯一 ID,第二个参数是 Store 的定义(可以是对象或函数)。
  2. ​​ref​​ 和 ​computed​​:在组合式风格中,我们使用 Vue 的响应式 API 来定义状态和计算属性,和写组件 script setup 一模一样。
  3. ​​return​​ 语句:组合式 Store 必须 return 一个对象,对象中的属性和方法才能被外部访问。

3.3 在组件中使用 Store

<template>
  <div>
    <h1>{{ counterStore.name }}</h1>
    <p>计数: {{ counterStore.count }}</p>
    <p>双倍计数: {{ counterStore.doubleCount }}</p>
    <p>用户名: {{ userStore.userName }}</p>

    <button @click="counterStore.increment()">增加</button>
    <button @click="counterStore.decrement()">减少</button>
    <button @click="counterStore.incrementBy(5)">增加5</button>
    <button @click="counterStore.incrementAsync()">异步增加</button>

    <button @click="login" v-if="!userStore.isLoggedIn">登录</button>
    <button @click="userStore.logout()" v-else>退出</button>
  </div>
</template>

<script lang="ts" setup>
import { watch } from 'vue'
import { useCounterStore } from '@/stores/counter'
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'

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

// 关键:使用 storeToRefs 保持解构后的响应性
const { count, doubleCount } = storeToRefs(counterStore)
const { userName, isLoggedIn } = storeToRefs(userStore)

// 直接调用 action(action 本身就是方法,无需解构)
const login = () => {
  userStore
    .login({ username: 'admin', password: 'password' })
    .then(() => {
      console.log('登录成功')
    })
    .catch((error) => {
      console.error('登录失败:', error)
    })
}

// 监听 Store 状态变化
watch(
  () => counterStore.count,
  (newCount, oldCount) => {
    console.log(`计数从 ${oldCount} 变为 ${newCount}`)
  }
)
</script>

代码解析:

  1. ​​storeToRefs​​:这是 Pinia 提供的一个工具函数,用于“解包” Store 的 state 和 getters。直接从 Store 解构 const { count } = counterStore​ 会失去响应性,而 storeToRefs​ 保证了解构后的每个 ref 依然是响应式的。
  2. 直接调用 ​actions​​:actions​ 是普通函数,可以直接从 Store 对象上调用,不需要解构。counterStore.increment()​ 和 userStore.login() 都是正确的用法。
  3. ​​watch​​ 监听:你可以像监听普通 ref​ 一样监听 Store 中的状态,使用 watch(() => counterStore.count, handler)。

刚接触 Pinia,建议先从最典型的状态(比如用户登录信息、全局配置)开始使用,不要一开始就把所有组件数据都放进 Store。写得顺手了,再逐步将那些频繁跨组件传递的 props 和 emit 改为 Store 管理,你会发现代码瞬间清爽许多。

到此这篇关于Vue3 状态管理 Pinia 入门指南的文章就介绍到这了,更多相关Vue3 状态管理 Pinia内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!‍

相关文章

  • vue解决子组件样式覆盖问题scoped deep

    vue解决子组件样式覆盖问题scoped deep

    文章主要介绍了在Vue项目中处理全局样式和局部样式的方法,包括使用scoped属性和深度选择器(/deep/)来覆盖子组件的样式,作者建议所有组件必须使用scoped,以避免样式冲突,对于父组件覆盖子组件的样式,作者推荐给子组件指定自定义类名
    2025-01-01
  • Vue Form表单的使用方法(rules格式校验网络校验键盘按键监听)

    Vue Form表单的使用方法(rules格式校验网络校验键盘按键监听)

    本文介绍了在Vue.js中使用表单校验规则(rules)进行网络请求校验的方法,以及如何通过formRef引用表单对象并进行键盘按键监听,感兴趣的朋友跟随小编一起看看吧
    2024-11-11
  • vue移动端使用appClound拉起支付宝支付的实现方法

    vue移动端使用appClound拉起支付宝支付的实现方法

    这篇文章主要介绍了vue移动端使用appClound拉起支付宝支付的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-11-11
  • vue导出PDF实现方式(vue2,插件:pdfmake,表格)

    vue导出PDF实现方式(vue2,插件:pdfmake,表格)

    文章主要介绍了如何使用pdfmake插件生成PDF文件,并提供了一个示例代码,用于将嵌套的数组转换为一维数组,以便在PDF中生成表格,同时,文章还讨论了如何设置表格的样式,包括颜色、背景色和文字位置等
    2026-03-03
  • vue双向绑定及观察者模式详解

    vue双向绑定及观察者模式详解

    这篇文章主要介绍了vue双向绑定及观察者模式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-03-03
  • 去除element-ui下拉框的下拉箭头的实现

    去除element-ui下拉框的下拉箭头的实现

    我们最开始拿到的element-ui是带有下拉箭头的,那么如何去除element-ui下拉框的下拉箭头的实现,本文就详细的介绍一下,感兴趣的可以了解一下
    2023-08-08
  • vue解决跨域问题的几种方式

    vue解决跨域问题的几种方式

    这篇文章主要给大家介绍了关于vue解决跨域问题的几种方式,跨域跨域报错是前端开发中非常经典的一个错误,文中通过代码示例介绍的非常详细,需要的朋友可以参考下
    2023-07-07
  • vue+swiper实现组件化开发的实例代码

    vue+swiper实现组件化开发的实例代码

    这篇文章主要介绍了vue+swiper实现组件化开发的相关资料,需要的朋友可以参考下
    2017-10-10
  • 详解在vue-cli项目中使用mockjs(请求数据删除数据)

    详解在vue-cli项目中使用mockjs(请求数据删除数据)

    本篇文章主要介绍了在vue-cli项目中使用mockjs(请求数据删除数据),具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-10-10
  • Vue 数据绑定从 v-bind 到 v-model 的实战案例

    Vue 数据绑定从 v-bind 到 v-model 的实战案例

    本文将系统解析Vue中两种核心数据绑定方式,单向绑定 (v-bind) 和双向绑定 (v-model),通过实例讲解其工作原理、使用场景及最佳实践,帮助你真正掌握 Vue 数据驱动的精髓,感兴趣的朋友跟随小编一起看看吧
    2025-10-10

最新评论