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 分钟前
前端怎么熟悉ES6+语法
前端
代码猎人3 分钟前
前端怎么精通Vue2核心原理
前端
夏天要喝冰可乐4 分钟前
我用 WorkBuddy 做了个「GitHub 考古所」:输入一个用户名,出土你的编程人格档案
前端·github
乘风gg8 分钟前
AI coding 时代最值钱的东西,软件工程(下)
前端·ai编程·claude
DGT17 分钟前
不用 LangChain,200 行 TypeScript 手搓一个能联网、能写文件的 Agent
前端·langchain·agent
用户114818678948419 分钟前
虚拟滚动与无限滚动的完整心法
前端
核验笔记的老王19 分钟前
前端表单防呆实践:如何排查自动填充导致的提交失败?
前端
用户4061220811120 分钟前
Vue vs React 核心差异全对比
前端
李纲明24 分钟前
微信小程序 web-view 打开空白或提示未配置业务域名:校验文件、HTTPS 与跳转域名排查
前端·微信小程序·https·web-view·小程序开发·业务域名
涛涛ing30 分钟前
一个框架为了“不水合”,把HTML注释都删了
前端