Pinia Actions and Getters
Organizing larger Pinia stores with async actions, cross-store composition, and the option-store alternative syntax.
読了時間 2 分
The previous lesson introduced a minimal Pinia store. Real stores usually need to fetch data asynchronously and coordinate several related pieces of state — this lesson covers the patterns for that.
Async actions
Actions are just functions on the store — nothing prevents them from being async:
// src/stores/users.js
import { defineStore } from "pinia";
import { ref } from "vue";
export const useUsersStore = defineStore("users", () => {
const users = ref([]);
const isLoading = ref(false);
const error = ref(null);
async function fetchUsers() {
isLoading.value = true;
error.value = null;
try {
const response = await fetch("/api/users");
users.value = await response.json();
} catch (err) {
error.value = err.message;
} finally {
isLoading.value = false;
}
}
return { users, isLoading, error, fetchUsers };
});<script setup>
import { onMounted } from "vue";
import { useUsersStore } from "../stores/users";
const usersStore = useUsersStore();
onMounted(() => usersStore.fetchUsers());
</script>
<template>
<p v-if="usersStore.isLoading">Loading...</p>
<p v-else-if="usersStore.error">{{ usersStore.error }}</p>
<ul v-else>
<li v-for="user in usersStore.users" :key="user.id">{{ user.name }}</li>
</ul>
</template>Centralizing this fetch in the store, rather than in the component's own onMounted, means any other component that needs the same user list can call usersStore.fetchUsers() (or just read usersStore.users if it's already been loaded) without duplicating the fetch logic itself.
Composing stores
Stores can use other stores, simply by calling their use*Store() function inside the store definition:
// src/stores/cart.js
import { defineStore } from "pinia";
import { ref, computed } from "vue";
import { useUsersStore } from "./users";
export const useCartStore = defineStore("cart", () => {
const items = ref([]);
const usersStore = useUsersStore();
const itemCount = computed(() => items.value.length);
const currentUserName = computed(() => usersStore.users[0]?.name);
function addItem(item) {
items.value.push(item);
}
return { items, itemCount, currentUserName, addItem };
});Pinia handles this without any special wiring — useUsersStore() inside another store returns the same shared instance as calling it from a component.
The option-store alternative
Everything above uses the setup store syntax (a function returning reactive state, mirroring <script setup>). Pinia also supports an option store syntax closer to Vuex or the Options API, using state, getters, and actions keys instead:
export const useCounterStore = defineStore("counter", {
state: () => ({ count: 0 }),
getters: {
doubled: (state) => state.count * 2,
},
actions: {
increment() {
this.count++;
},
},
});Both compile down to the same thing internally, and you can mix styles across different stores in one project. This course favors the setup-store syntax throughout, since it's a direct extension of the Composition API you've already been using rather than a second API to learn — but you'll likely encounter the option-store style reading other projects' Pinia code, so it's worth being able to recognize.