Pinia

Action 与异步操作

掌握在 Pinia actions 中处理异步请求、调用其他 action、管理加载状态等实战技巧。

🎯 引言

学会这篇文章,你能在 Pinia 的 actions 中处理接口请求等异步操作。我们会用一个最简例子讲清楚核心流程,并解释 action 中 this 到底指向哪里。


🧱 为什么异步要放到 action 里

假设你要从后台拉取用户信息,然后显示在多个地方。如果每个组件都自己发请求:

  • 代码会重复。
  • 加载状态和错误处理分散。
  • 组件会变得很臃肿。

把请求放到 Store 的 action 中,组件只需要调用一个方法,剩下的交给 Store。


🛠 在 action 中发请求

Pinia 的 action 支持 async/await,写法很自然:

stores/user.js
import { defineStore } from 'pinia';
import { fetchUserInfo } from '../api/user.js';

export const useUserStore = defineStore('user', {
    state: () => ({
        info: null,
        loading: false,
    }),
    actions: {
        async loadUser() {
            this.loading = true;

            try {
                const res = await fetchUserInfo();
                this.info = res.data;
            } finally {
                this.loading = false;
            }
        },
    },
});

组件中使用:

App.vue
<template>
    <p v-if="user.loading">加载中...</p>
    <p v-else>用户名:{{ user.info?.name }}</p>
    <button @click="user.loadUser">获取用户</button>
</template>

<script setup>
import { useUserStore } from './stores/user.js';

const user = useUserStore();
</script>
action 里用 this.infothis.loading 读写 state。这里的 this 指向当前 Store 实例,不是组件实例。

⚡ action 里调用其他 action

一个 action 可以调用同一个 Store 里的其他 action:

stores/counter.js
export const useCounterStore = defineStore('counter', {
    state: () => ({
        count: 0,
    }),
    actions: {
        add() {
            this.count++;
        },
        async addAndLog() {
            this.add(); // 调用本 Store 的 action
            console.log('当前计数:', this.count);
        },
    },
});
调用本 Store 的 action 直接用 this.xxx(),和调用普通方法一样。

💡 跨 Store 调用

有时候一个 action 需要用到另一个 Store 的数据或方法。比如在登录成功后刷新购物车:

stores/auth.js
import { defineStore } from 'pinia';
import { useCartStore } from './cart.js';

export const useAuthStore = defineStore('auth', {
    actions: {
        async login(form) {
            const res = await apiLogin(form);
            this.token = res.token;

            const cart = useCartStore();
            await cart.loadCart();
        },
    },
});
在 action 内部调用 useCartStore() 是安全的,因为 Pinia 已经注册好了。但不要在 Store 文件的最外层直接调用 use 函数。

🧾 小节总结

  • 异步请求建议放到 Store 的 action 中,组件只负责触发和展示。
  • action 中 this 指向 Store 实例,用 this.xxx 读写 state。
  • action 支持 async/await
  • 一个 action 可以调用本 Store 的其他 action,也可以调用其他 Store 的 action。

❓ 知识问答

Q1:action 里的 this 指向组件吗?

不指向组件。this 指向当前的 Store 实例,所以你可以用 this.stateNamethis.actionName()

Q2:action 中调用其他 Store 的 action 会创建新实例吗?

不会。useXxxStore() 返回的是单例,多次调用拿到的是同一个实例。

Q3:可以在 action 里直接修改其他 Store 的 state 吗?

技术上可以,但不推荐。应该让那个 Store 自己提供 action 来修改自己的状态,这样职责更清晰。

Q4:action 一定要是异步的吗?

不一定。同步逻辑也可以放到 action 中,只是异步场景更需要它。


🧪 小练习

补全下面的 useArticleStore,实现:

  1. articles 初始为空数组,loadingfalse
  2. fetchArticles() action 先设置 loadingtrue,1 秒后把模拟数据放入 articles,再把 loading 设为 false
stores/article.js
import { defineStore } from 'pinia';

function mockFetch() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve([
                { id: 1, title: 'Pinia 入门' },
                { id: 2, title: 'Vue3 新特性' },
            ]);
        }, 1000);
    });
}

export const useArticleStore = defineStore('article', {
    state: () => ({
        // 请在这里定义初始状态
    }),
    actions: {
        async fetchArticles() {
            // 请在这里编写代码
        },
    },
});

🎉 恭喜你掌握了 Pinia 的异步 action!下一篇我们学习多 Store 组织、TypeScript 和插件扩展。