Pinia 状态管理实战:定义 Store、修改数据、模块拆分

一、Pinia 安装与注册

Pinia 是 Vue3 官方推荐的状态管理库,取代 Vuex:API 更简洁、天然支持 TS、无 mutations。

bash 复制代码
npm install pinia
ts 复制代码
// main.ts
import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";

const app = createApp(App);
app.use(createPinia());
app.mount("#app");

与 Vuex 对比

对比项 Vuex Pinia
mutations 必须有(同步改 state) 没有,直接改 state / actions
TypeScript 类型推导弱 类型友好
模块 modules 嵌套 每个 store 独立定义
体积 较大 更轻(仅 ~1KB core)

二、defineStore 定义仓库

两种风格:选项式 (对象)与 setup 风格(函数),推荐 setup 风格。

2.1 选项式风格

ts 复制代码
// stores/user.ts
import { defineStore } from "pinia";

export const useUserStore = defineStore("user", {
  state: () => ({
    name: "",
    token: localStorage.getItem("token") || "",
  }),
  getters: {
    isLogin: (state) => !!state.token,       // 派生状态
  },
  actions: {
    setToken(token: string) {
      this.token = token;
    },
  },
});

2.2 setup 风格(推荐)

ts 复制代码
// stores/counter.ts
import { ref, computed } from "vue";
import { defineStore } from "pinia";

export const useCounterStore = defineStore("counter", () => {
  // state
  const count = ref(0);
  const double = ref(2);

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

  // actions
  function increment() {
    count.value++;
  }

  return { count, double, doubleCount, increment };
});
构成 选项式写法 setup 风格写法
state state: () => ({...}) ref() / reactive()
getters getters: { xx(state) {...} } computed()
actions actions: { xx() {...} } 普通函数

三、组件中读取与修改 store

3.1 基础读写

vue 复制代码
<script setup lang="ts">
import { useCounterStore } from "@/stores/counter";

const store = useCounterStore();

store.count++;            // 直接修改 state(无需 mutations)
store.increment();        // 调用 action
</script>

<template>
  <p>{{ store.count }} × 2 = {{ store.doubleCount }}</p>
  <button @click="store.increment()">+1</button>
</template>

3.2 $patch 批量修改

ts 复制代码
store.$patch({
  count: store.count + 1,
  double: store.count * 2,
});

// 函数式写法:可写逻辑
store.$patch((state) => {
  state.count++;
  state.double = state.count * 2;
});

3.3 storeToRefs 保持响应式

ts 复制代码
import { storeToRefs } from "pinia";
import { useCounterStore } from "@/stores/counter";

const store = useCounterStore();
const { count, doubleCount } = storeToRefs(store);   // 解构后仍是响应式
const { increment } = store;                          // action 直接解构

四、store 之间互相调用

ts 复制代码
// stores/order.ts
import { defineStore } from "pinia";
import { useUserStore } from "./user";

export const useOrderStore = defineStore("order", () => {
  const userStore = useUserStore();      // 在另一个 store 内直接使用

  const orders = ref<number[]>([]);

  function fetchOrders() {
    // 读取其他 store 的状态
    if (!userStore.isLogin) return;
    orders.value = [1, 2, 3];
  }

  return { orders, fetchOrders };
});

五、异步请求写在 actions

ts 复制代码
// stores/article.ts
import { ref } from "vue";
import { defineStore } from "pinia";
import api from "@/api";           // 封装好的请求

export const useArticleStore = defineStore("article", () => {
  const list = ref<Article[]>([]);
  const loading = ref(false);

  async function fetchList(params: Query) {
    loading.value = true;
    try {
      list.value = await api.getArticleList(params);   // actions 支持 async/await
    } finally {
      loading.value = false;
    }
  }

  return { list, loading, fetchList };
});
vue 复制代码
<!-- 组件中 -->
<script setup lang="ts">
import { onMounted } from "vue";
import { useArticleStore } from "@/stores/article";
import { storeToRefs } from "pinia";

const store = useArticleStore();
const { list, loading } = storeToRefs(store);

onMounted(() => store.fetchList({ page: 1 }));
</script>

六、踩坑:解构丢失响应式

坑 现象 解决
直接解构 state const { count } = store 后页面不更新 用 storeToRefs(store)
解构 getters 同上 storeToRefs
直接解构 actions 丢失 this 上下文报错(选项式) action 直接 const { fn } = store(setup 风格无此问题)
非响应式坑 store 中存了普通对象(非 ref)被替换 用 ref/reactive 包裹
$reset 不生效 setup 风格 store 无内置 $reset 手写 reset 函数返回初始值
组件外使用 store 在普通工具函数中调用报错(未装 pinia) 确保在 app.use(pinia) 后调用
ts 复制代码
// 正确解构姿势
const { count, doubleCount } = storeToRefs(store);   // state/getters
const { increment } = store;                          // actions

七、总结

  • 定位:Vue3 官方状态管理,无 mutations、TS 友好,取代 Vuex;
  • 定义 :defineStore 选项式或 setup 风格,state/getters/actions 三件套;
  • 读写 :直接改 state、$patch 批量、actions 承载异步;
  • 跨 store :store 内直接 useXxxStore() 互相调用;
  • 异步:actions 用 async/await 写请求,loading 状态一并管理;
  • 踩坑 :解构必须 storeToRefs,actions 直接解构,setup 风格 $reset 需手写。

本系列前篇:《Vue3 零基础上手》《Vue3 组合式 API 深度解析》《Vue3 组件开发》《Vue Router4 完整指南》;后续:《Vue3 项目工程实战》。

相关推荐
星栈7 小时前
pnpm 12 升级实测
前端·javascript
Sirens.7 小时前
Java并发锁详解:六类锁策略与 synchronized 底层原理
java·前端·算法
特创数字科技9 小时前
一个纯本地运行的图片处理工具:压缩 / 裁剪 / 九宫格 / 圆角 / 滤镜 / 拼图 / 水印,终生使用
前端
IT_陈寒10 小时前
Vue的v-if和v-for混用居然是个天坑
前端·人工智能·后端
天衍四九-10 小时前
【无标题】
前端·spring boot·mysql·nginx·docker
广州华水科技10 小时前
2026年单北斗GNSS变形监测系统推荐榜单,解锁GNSS位移监测新高度
前端
xcyxiner10 小时前
DicomViewer24 修复编译bug(window test 失败)
前端·qt
linux_cfan10 小时前
videojs v10 源代码系列解读:34 · `createComposition`:类型安全的冲突检测
前端·安全
utmhikari11 小时前
【AI原生】借助AI之力优化个人博客前端样式
前端·css·hexo·next·个人博客·ai-native·vibe coding