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 项目工程实战》。

相关推荐
高申航1 小时前
Vue 3 + DYMO Connect Framework 实战:从标签模板到打印服务的完整实现
前端·javascript·vue.js
梨想橙汁1 小时前
Vue3 组件开发:生命周期钩子,组件之间通信全方案
前端·vue.js
雪芽蓝域zzs1 小时前
分页组件 + el-config-provider 中文国际化 + 靠右布局
前端·javascript·vue.js
恋猫de小郭2 小时前
iPhone Duo 适配详解,需要改变的不止是布局模型
前端·flutter·ios
萧行之2 小时前
Observable Plot 源码深度解析——4 核心抽象逐项学习
前端·学习·数据可视化
今日无bug2 小时前
为什么大模型回答总是一个字一个字蹦出来?聊聊 SSE 流式输出与 BFF 层的那些事
前端·llm
志尊宝2 小时前
Vue3 零基础每日笔记(016):v-for 列表渲染——key 的作用与为什么不能用 index
javascript·vue.js·笔记
CIO_Alliance2 小时前
AI微调系列(1)| 全量微调、LoRA、QLoRA 三种方案怎么选
前端·人工智能·神经网络·机器学习·embedding·企业ai转型
hanbo17C22 小时前
企业官网能不能看出公司实力?选建站公司前,先看这6条硬标准
运维·服务器·前端