Chapter 05

Actions 动作

Actions 承载业务逻辑——同步改状态、异步请求数据、跨 store 组合、集中错误处理,是 store 的"方法层"。

5.1 Actions 核心概念

Action
store 中修改状态、执行业务逻辑的方法,等价于组件的 methods,可同步可异步。
this 上下文
选项式 action 用普通函数定义,this 指向整个 store,可读写 state、调用其它 action。
异步 action
async/await 处理请求,返回 Promise,组件可 await 其结果。
跨 store 组合
一个 action 内导入并调用另一个 store,实现 store 之间协作。
错误处理
action 内用 try/catch 捕获异常,或让 Promise reject 由调用方处理。
无 mutations
Pinia 中 action 可直接改 state,不像 Vuex 需要在 action 里 commit mutation。

5.2 同步 Action

export const useCounterStore = defineStore("counter", {
  state: () => ({ count: 0 }),
  actions: {
    increment() {
      this.count++;            // this 指向 store
    },
    incrementBy(step: number) {
      this.count += step;
    },
    reset() {
      this.count = 0;
    },
  },
});
action 别用箭头函数

选项式 action 必须用普通函数(increment() {}),箭头函数会让 this 指向错误的上下文,拿不到 store。

5.3 异步 Action

export const useUserStore = defineStore("user", {
  state: () => ({ profile: null as Profile | null, loading: false }),
  actions: {
    async fetchProfile(id: number) {
      this.loading = true;
      try {
        const res = await fetch(`/api/users/${id}`);
        if (!res.ok) throw new Error("请求失败");
        this.profile = await res.json();
        return this.profile;      // 可返回给调用方
      } finally {
        this.loading = false;
      }
    },
  },
});
// 组件中调用并等待
const user = useUserStore();
await user.fetchProfile(1);
console.log(user.profile);

5.4 调用其它 Action

actions: {
  async login(email: string, pwd: string) {
    const { token, id } = await api.login(email, pwd);
    this.token = token;
    await this.fetchProfile(id);   // 调用本 store 的另一个 action
  },
},

5.5 跨 Store 组合

一个 store 的 action 里可以使用另一个 store——直接在函数内调用它的 use 函数:

import { defineStore } from "pinia";
import { useAuthStore } from "./auth";

export const useCartStore = defineStore("cart", {
  state: () => ({ items: [] as Item[] }),
  actions: {
    async checkout() {
      const auth = useAuthStore();   // 在 action 内取,保证已激活
      if (!auth.isLoggedIn) throw new Error("请先登录");

      await api.createOrder(auth.userId, this.items);
      this.items = [];
    },
  },
});
为何在函数内部取 store

useAuthStore() 写在 action 内部(而非模块顶层),能保证 Pinia 已激活、且 SSR 每个请求拿到正确实例。顶层调用会在 Pinia 挂载前执行而报错。

5.6 错误处理策略

store 内吞掉错误

action 里 try/catch,把错误写进 state.error,组件读 state 显示提示。适合统一错误 UI。

向上抛给组件

action 不 catch,让 Promise reject,组件 try { await ... } 自行处理。适合针对性交互。

// 组件中捕获抛出的错误
try {
  await cart.checkout();
  toast.success("下单成功");
} catch (e) {
  toast.error((e as Error).message);
}

5.7 组合式写法的 Action

export const useUserStore = defineStore("user", () => {
  const profile = ref<Profile | null>(null);
  const loading = ref(false);

  // 普通函数即 action,直接操作 ref
  async function fetchProfile(id: number) {
    loading.value = true;
    try {
      profile.value = await (await fetch(`/api/users/${id}`)).json();
    } finally {
      loading.value = false;
    }
  }

  return { profile, loading, fetchProfile };
});

5.8 小结与下一步