pinia状态管理在vue3项目中的用法详解

Pinia 是 Vue3 官方推荐的状态管理库(替代 Vuex),相比 Vuex 更简洁、轻量化,原生支持组合式 API,无嵌套模块限制,且完美兼容 Vue DevTools。下面时pinia官方的介绍:

下面介绍一下 Pinia这款状态管理工具的完整用法,实际项目在的大多数场景可以直接照着代码改一下就可以用了。

一、核心优势(为什么选 Pinia)

  1. 无需嵌套模块,天然支持「扁平化模块化」;
  2. 抛弃 mutation(仅需 state/getters/actions),简化状态修改逻辑;
  3. 原生支持 TypeScript,类型提示更友好;
  4. 支持组合式 API(setup)和选项式 API 两种写法;
  5. 体积更小(约 1KB),性能更优;
  6. 完美兼容 Vue DevTools(支持时间旅行、状态追踪)。

二、基础安装

bash 复制代码
# npm
npm install pinia

# yarn
yarn add pinia

# pnpm
pnpm add pinia

三、全局挂载 Pinia 实例

main.js 中创建并挂载 Pinia 实例(所有组件可共享 Store):

javascript 复制代码
// src/main.js (Vue3 + Vite)
import { createApp } from 'vue';
import { createPinia } from 'pinia'; // 导入 Pinia
import App from './App.vue';

const app = createApp(App);
const pinia = createPinia(); // 创建 Pinia 实例
app.use(pinia); // 挂载到 Vue 应用
app.mount('#app');

四、定义 Store(核心)

Pinia 中「Store」是状态管理的核心单元,每个业务模块(如用户、购物车)对应一个独立的 Store。

支持两种写法:选项式 API(类似 Vuex)组合式 API(更灵活)

1. 选项式 API 写法(推荐新手)
javascript 复制代码
// src/stores/counterStore.js
import { defineStore } from 'pinia';

// 第一个参数:Store 唯一标识(必须唯一),第二个参数:配置对象
export const useCounterStore = defineStore('counter', {
  // 1. 状态:返回初始值的函数(类似 Vue 的 data)
  state: () => ({
    count: 0,
    title: 'Pinia 计数器',
    userInfo: { name: '张三', age: 20 }
  }),

  // 2. 计算属性:类似 Vue 的 computed,缓存结果
  getters: {
    // 基础用法:基于 state 计算
    doubleCount: (state) => state.count * 2,

    // 依赖其他 getter(通过 this 访问)
    doubleCountPlusOne() {
      return this.doubleCount + 1;
    },

    // 带参数的 getter(返回函数)
    getCountByMultiplier: (state) => (multiplier) => {
      return state.count * multiplier;
    }
  },

  // 3. 方法:支持同步/异步,修改状态(类似 Vue 的 methods)
  actions: {
    // 同步修改
    increment() {
      this.count++; // 直接修改 state,无需 mutation
    },

    // 带参数的同步方法
    incrementBy(num) {
      this.count += num;
    },

    // 异步方法(如接口请求)
    async fetchData() {
      const res = await fetch('https://api.example.com/count');
      const data = await res.json();
      this.count = data.count; // 异步修改状态
    },

    // 调用其他 action
    async fetchAndIncrement() {
      await this.fetchData(); // 调用自身异步 action
      this.increment(); // 调用同步 action
    }
  }
});
2. 组合式 API 写法(推荐 Vue3 组合式项目)

更灵活,可复用组合式函数(composables):

javascript 复制代码
// src/stores/counterStore.js
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';

// 组合式 API 写法:返回函数
export const useCounterStore = defineStore('counter', () => {
  // 1. 状态:等价于 state(用 ref/reactive 定义)
  const count = ref(0);
  const title = ref('Pinia 计数器');
  const userInfo = ref({ name: '张三', age: 20 });

  // 2. 计算属性:等价于 getters(用 computed 定义)
  const doubleCount = computed(() => count.value * 2);
  const doubleCountPlusOne = computed(() => doubleCount.value + 1);
  const getCountByMultiplier = (multiplier) => count.value * multiplier;

  // 3. 方法:等价于 actions(普通函数)
  const increment = () => {
    count.value++;
  };
  const incrementBy = (num) => {
    count.value += num;
  };
  const fetchData = async () => {
    const res = await fetch('https://api.example.com/count');
    const data = await res.json();
    count.value = data.count;
  };

  // 必须返回需要暴露的状态/方法
  return {
    count,
    title,
    userInfo,
    doubleCount,
    doubleCountPlusOne,
    getCountByMultiplier,
    increment,
    incrementBy,
    fetchData
  };
});

五、组件中使用 Store

1. 基础使用(组合式 API 组件)
vue 复制代码
<!-- src/components/Counter.vue -->
<template>
  <div>
    <h3>{{ counterStore.title }}</h3>
    <p>当前计数:{{ counterStore.count }}</p>
    <p>双倍计数:{{ counterStore.doubleCount }}</p>
    <p>三倍计数:{{ counterStore.getCountByMultiplier(3) }}</p>

    <button @click="counterStore.increment">+1</button>
    <button @click="counterStore.incrementBy(5)">+5</button>
    <button @click="counterStore.fetchData">异步获取计数</button>
    <button @click="resetCount">重置计数</button>
  </div>
</template>

<script setup>
// 1. 导入 Store 函数
import { useCounterStore } from '@/stores/counterStore';

// 2. 创建 Store 实例(Pinia 会自动复用单例,多次调用也返回同一个实例)
const counterStore = useCounterStore();

// 3. 直接修改状态(Pinia 默认允许,可通过严格模式禁止)
const resetCount = () => {
  counterStore.count = 0;
};
</script>
2. 解构 Store 保持响应式(关键)

直接解构 Store 会丢失响应式,需用 storeToRefs 辅助:

vue 复制代码
<script setup>
import { useCounterStore } from '@/stores/counterStore';
import { storeToRefs } from 'pinia'; // 导入辅助函数

const counterStore = useCounterStore();

// 错误:解构后丢失响应式
// const { count, doubleCount } = counterStore;

// 正确:用 storeToRefs 解构,保留响应式
const { count, doubleCount, title } = storeToRefs(counterStore);

// actions 方法无需解构(本身是函数,不涉及响应式)
const { increment, fetchData } = counterStore;
</script>

<template>
  <div>
    <p>{{ title }}</p>
    <p>{{ count }}</p>
    <p>{{ doubleCount }}</p>
    <button @click="increment">+1</button>
    <button @click="fetchData">异步获取</button>
  </div>
</template>
3. 选项式 API 组件中使用
vue 复制代码
<script>
import { useCounterStore } from '@/stores/counterStore';
import { mapState, mapActions } from 'pinia'; // 导入映射辅助函数

export default {
  computed: {
    // 映射 state/getters 到计算属性
    ...mapState(useCounterStore, ['count', 'doubleCount', 'title'])
  },
  methods: {
    // 映射 actions 到方法
    ...mapActions(useCounterStore, ['increment', 'fetchData'])
  },
  mounted() {
    console.log(this.count); // 访问状态
    this.increment(); // 调用方法
  }
};
</script>

六、模块化(多 Store 管理)

Pinia 天然支持模块化,每个业务模块对应一个独立的 Store,无需嵌套:

复制代码
src/stores/
├── counterStore.js  // 计数器 Store
├── userStore.js     // 用户 Store
├── cartStore.js     // 购物车 Store
示例:用户 Store
javascript 复制代码
// src/stores/userStore.js
import { defineStore } from 'pinia';

export const useUserStore = defineStore('user', {
  state: () => ({
    token: '',
    userInfo: null,
    permissions: []
  }),
  actions: {
    // 登录
    async login(username, password) {
      const res = await fetch('/api/login', {
        method: 'POST',
        body: JSON.stringify({ username, password })
      });
      const data = await res.json();
      this.token = data.token;
      this.userInfo = data.userInfo;
      this.permissions = data.permissions;
    },
    // 退出登录
    logout() {
      this.token = '';
      this.userInfo = null;
      this.permissions = [];
    }
  }
});
组件中按需导入多 Store
vue 复制代码
<script setup>
import { useCounterStore } from '@/stores/counterStore';
import { useUserStore } from '@/stores/userStore';

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

// 调用不同 Store 的方法
const handleLogin = async () => {
  await userStore.login('admin', '123456');
  counterStore.increment();
};
</script>

七、进阶用法

1. 状态持久化(本地存储)

安装插件 pinia-plugin-persistedstate,可以实现 Store 状态自动持久化到 localStorage/sessionStorage

bash 复制代码
npm install pinia-plugin-persistedstate
javascript 复制代码
// src/main.js
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'; // 导入插件
import App from './App.vue';

const pinia = createPinia();
pinia.use(piniaPluginPersistedstate); // 注册插件

const app = createApp(App);
app.use(pinia);
app.mount('#app');

在 Store 中开启持久化:

javascript 复制代码
// 选项式 API 写法
export const useUserStore = defineStore('user', {
  state: () => ({ token: '', userInfo: null }),
  actions: { /* ... */ },
  persist: true, // 开启持久化(默认存储到 localStorage)
});

// 自定义持久化配置
export const useCartStore = defineStore('cart', {
  state: () => ({ items: [] }),
  persist: {
    key: 'cart-store', // 自定义存储键名
    storage: sessionStorage, // 存储到 sessionStorage
    paths: ['items'], // 仅持久化 items 字段(默认全量)
  },
});
2. Store 间通信

一个 Store 可直接调用另一个 Store 的状态/方法:

javascript 复制代码
// src/stores/cartStore.js
import { defineStore } from 'pinia';
import { useUserStore } from './userStore'; // 导入其他 Store

export const useCartStore = defineStore('cart', {
  state: () => ({ items: [] }),
  actions: {
    async addItem(item) {
      const userStore = useUserStore(); // 创建其他 Store 实例
      if (!userStore.token) {
        throw new Error('未登录,无法添加商品');
      }
      // 调用接口添加商品
      await fetch('/api/cart/add', {
        method: 'POST',
        headers: { Authorization: userStore.token },
        body: JSON.stringify(item)
      });
      this.items.push(item);
    }
  }
});
3. 严格模式(禁止直接修改状态)

Pinia 默认允许直接修改 state,开启严格模式后,仅允许通过 actions 修改状态(类似 Vuex 的严格模式):

javascript 复制代码
// src/main.js
import { createPinia } from 'pinia';

const pinia = createPinia();

// 开启严格模式(仅生产环境建议关闭)
if (import.meta.env.MODE !== 'production') {
  pinia.use(({ store }) => {
    store.$subscribe((mutation, state) => {
      // 检测是否直接修改 state(非 actions 触发)
      if (mutation.type === 'direct') {
        console.warn(`[Pinia 严格模式] 禁止直接修改 ${store.$id} 的状态:${mutation.payload}`);
      }
    });
  });
}
4. 状态重置

调用 $reset() 方法重置 Store 到初始状态:

vue 复制代码
<script setup>
import { useCounterStore } from '@/stores/counterStore';

const counterStore = useCounterStore();

const resetStore = () => {
  counterStore.$reset(); // 重置所有状态到初始值
};
</script>
5. 批量修改状态

使用 $patch 批量修改状态(性能更优,DevTools 会记录为一次修改):

javascript 复制代码
// 方式1:对象形式
counterStore.$patch({
  count: 10,
  title: '新标题'
});

// 方式2:函数形式(支持复杂逻辑)
counterStore.$patch((state) => {
  state.count += 5;
  state.userInfo.age += 1;
});

八、调试技巧

  1. Vue DevTools:Pinia 完美兼容 DevTools,可在「Pinia」面板查看所有 Store 的状态,支持「时间旅行」(回溯状态修改记录);

  2. $subscribe 监听状态变化

    javascript 复制代码
    const counterStore = useCounterStore();
    // 监听状态变化
    counterStore.$subscribe((mutation, state) => {
      console.log('状态变化:', mutation.type, mutation.payload, state);
    });
  3. $onAction 监听 action 调用

    javascript 复制代码
    counterStore.$onAction(({ name, args, after, onError }) => {
      console.log(`开始执行 action:${name},参数:${args}`);
      after((result) => {
        console.log(`action ${name} 执行成功,结果:${result}`);
      });
      onError((error) => {
        console.error(`action ${name} 执行失败:${error}`);
      });
    });

九、常见问题

  1. 解构 Store 丢失响应式 :用 storeToRefs 解构状态,方法可直接解构;
  2. Store 实例重复创建 :无需担心,Pinia 会自动复用单例,多次调用 useCounterStore() 始终返回同一个实例;
  3. TypeScript 类型提示 :选项式 API 自动推导类型,组合式 API 需手动标注(或用 defineStore 自动推导);
  4. SSR 适配:Pinia 原生支持 SSR,需在服务端为每个请求创建独立的 Pinia 实例,避免状态污染。

总结

Pinia 的核心用法可总结为:

  1. 安装并挂载 Pinia 实例;
  2. defineStore 定义 Store(选项式/组合式);
  3. 组件中导入并创建 Store 实例,通过 storeToRefs 解构响应式状态;
  4. actions 处理同步/异步状态修改,getters 处理计算属性;
  5. 多 Store 实现模块化,插件扩展持久化、调试等能力。

相比 Vuex的繁琐,Pinia 更简洁、灵活,是 Vue3 项目状态管理的首选,建议在中大型项目中使用,小型项目可根据项目实际情况选择更加合适的。

相关推荐
zhuà!2 小时前
腾讯地图TMap标记反显,新增标记
前端·javascript·vue.js
未知原色2 小时前
web worker使用总结(包含多个worker)
前端·javascript·react.js·架构·node.js
ttod_qzstudio3 小时前
CSS改变图片颜色方法介绍
前端·css
幽络源小助理3 小时前
SpringBoot+Vue摄影师分享社区源码 – Java项目免费下载 | 幽络源
java·vue.js·spring boot
curdcv_po3 小时前
我接入了微信小说小程序官方阅读器
前端·微信小程序
程序员鱼皮3 小时前
什么是 RESTful API?凭什么能流行 20 多年?
前端·后端·程序员
+VX:Fegn08953 小时前
计算机毕业设计|基于springboot + vue健身房管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
www_stdio3 小时前
让大语言模型拥有“记忆”:多轮对话与 LangChain 实践指南
前端·langchain·llm
inferno3 小时前
JavaScript 基础
开发语言·前端·javascript