Chapter 02

定义 Store

掌握 defineStore 的两种写法——选项式贴近 Vuex 习惯,组合式用 ref/computed 更灵活,并理解如何选择。

2.1 defineStore 签名

defineStore 是 Pinia 的核心 API,返回一个"use 函数"。它有两种调用形式:

defineStore(id, options)
选项式:第二个参数是一个包含 state/getters/actions 的对象。结构清晰,接近 Vuex。
defineStore(id, setup)
组合式(Setup Store):第二个参数是一个 setup 函数,用 ref/computed/普通函数定义状态与逻辑,最后 return 暴露。
id(Store ID)
字符串,全局唯一。DevTools 显示、SSR 状态序列化、持久化插件都靠它区分不同 store。
use 函数
defineStore 的返回值,习惯命名 useXxxStore。在组件 setup 中调用它才拿到 store 实例。
Store 实例
调用 use 函数得到的响应式对象,直接读写 state、访问 getters、调用 actions。
选项对象里的 id
也可以把 id 写进选项对象:defineStore({ id: "counter", state, ... }),效果等同第一个参数传 id。

2.2 选项式 Store(Options Store)

// stores/user.ts —— 选项式写法
import { defineStore } from "pinia";

export const useUserStore = defineStore("user", {
  // state 必须是箭头函数返回对象(SSR 需要每次新建)
  state: () => ({
    name: "",
    age: 0,
    roles: [] as string[],
  }),
  // getters 接收 state,返回派生值
  getters: {
    isAdult: (state) => state.age >= 18,
    displayName: (state) => state.name || "匿名",
  },
  // actions 用普通函数(不要用箭头),才能拿到 this
  actions: {
    setName(name: string) {
      this.name = name;
    },
    async fetchUser(id: number) {
      const res = await fetch(`/api/user/${id}`);
      const data = await res.json();
      this.name = data.name;
      this.age = data.age;
    },
  },
});
state 必须是函数

选项式的 state 一定要写成返回对象的箭头函数 () => ({...}),不能直接给对象。SSR 场景下每个请求要一份全新的 state,函数才能保证不共享引用。

2.3 组合式 Store(Setup Store)

组合式写法把 store 当成一个 setup 函数:ref 就是 state,computed 就是 getter,普通函数就是 action,最后 return 出去。

// stores/user.ts —— 组合式写法(等价上面)
import { defineStore } from "pinia";
import { ref, computed } from "vue";

export const useUserStore = defineStore("user", () => {
  // state
  const name = ref("");
  const age = ref(0);
  const roles = ref<string[]>([]);

  // getters
  const isAdult = computed(() => age.value >= 18);
  const displayName = computed(() => name.value || "匿名");

  // actions
  function setName(v: string) {
    name.value = v;
  }
  async function fetchUser(id: number) {
    const res = await fetch(`/api/user/${id}`);
    const data = await res.json();
    name.value = data.name;
    age.value = data.age;
  }

  // 必须 return 才会暴露
  return { name, age, roles, isAdult, displayName, setName, fetchUser };
});
组合式必须 return 全部 state

没有被 returnref 不会成为 store 的 state,也不会被 DevTools、SSR、持久化识别。为了 SSR 正确工作,所有 state ref 都必须 return

2.4 两种写法映射关系

选项式组合式作用
state: () => ({ x: 0 })const x = ref(0)响应式状态
getters: { g: s => ... }const g = computed(() => ...)派生计算值
actions: { a() {...} }function a() {...}修改状态的方法
this.xx.value访问 state
(自动暴露)return { ... }暴露成员

2.5 如何选择

选项式适合

• 团队从 Vuex 迁移,习惯 state/getters/actions 分区
• 结构简单、成员固定的 store
• 希望约束统一、可读性高

组合式适合

• 需要 watchonMounted 等组合式 API
• 逻辑复杂、想复用组合函数
• 团队已全面拥抱 <script setup>

官方建议

两种写法功能完全等价,团队内保持一致最重要。新项目若已全面使用组合式 API,推荐 Setup Store;从 Vuex 迁移则用选项式过渡更平滑。

2.6 组合式 Store 的额外能力

组合式 store 能直接使用其它组合式 API,这是选项式做不到的:

import { defineStore } from "pinia";
import { ref, watch } from "vue";
import { useLocalStorage } from "@vueuse/core";

export const useSettingsStore = defineStore("settings", () => {
  // 直接用 VueUse,自动同步 localStorage
  const theme = useLocalStorage("theme", "light");

  watch(theme, (v) => {
    document.documentElement.dataset.theme = v;
  });

  return { theme };
});

2.7 小结与下一步