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

8 阅读1分钟

一、Pinia 安装与注册

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

npm install pinia
// 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 对比

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

二、defineStore 定义仓库

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

2.1 选项式风格

// 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 风格(推荐)

// 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 风格写法
statestate: () => ({...})ref() / reactive()
gettersgetters: { xx(state) {...} }computed()
actionsactions: { xx() {...} }普通函数

三、组件中读取与修改 store

3.1 基础读写

<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 批量修改

store.$patch({
  count: store.count + 1,
  double: store.count * 2,
});

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

3.3 storeToRefs 保持响应式

import { storeToRefs } from "pinia";
import { useCounterStore } from "@/stores/counter";

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

四、store 之间互相调用

// 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

// 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 };
});
<!-- 组件中 -->
<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>

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

现象解决
直接解构 stateconst { 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) 后调用
// 正确解构姿势
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 项目工程实战》。