Pinia Basics
Setting up centralized state with Pinia, and when it's a better fit than provide/inject or local component state.
អាន 2 នាទី
provide/inject solves sharing data down through the component tree, but it's not really designed for state that many unrelated parts of an app need to both read and write — a logged-in user, a shopping cart, a notification queue. Pinia is Vue's official state management library, built for exactly that.
Installing and setting up
npm install pinia// src/main.js
import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";
createApp(App).use(createPinia()).mount("#app");Same plugin pattern as Vue Router — use(createPinia()) registers it once, app-wide.
Defining a store
A Pinia store is defined with defineStore, given a unique ID as its first argument:
// src/stores/counter.js
import { defineStore } from "pinia";
import { ref, computed } from "vue";
export const useCounterStore = defineStore("counter", () => {
const count = ref(0);
const doubled = computed(() => count.value * 2);
function increment() {
count.value++;
}
return { count, doubled, increment };
});This is the setup store style — it looks exactly like <script setup> code, using ref, computed, and plain functions, and explicitly returns whatever should be part of the store's public surface. If you've absorbed the Composition API lessons earlier in this course, you already know how to write this — a Pinia store is really just reactive state defined outside any single component, so multiple components can share the same instance of it.
Using a store in a component
<script setup>
import { useCounterStore } from "../stores/counter";
const counterStore = useCounterStore();
</script>
<template>
<p>{{ counterStore.count }} (doubled: {{ counterStore.doubled }})</p>
<button @click="counterStore.increment()">+1</button>
</template>Every component that calls useCounterStore() gets the same underlying reactive state — not a fresh copy each time. Click the button from one component, and any other component displaying counterStore.count updates too, with no props or events wiring them together at all.
When Pinia beats the alternatives
- Local
ref/reactivestate is right when only one component (and maybe its direct children, via props) needs it. provide/injectis right for read-mostly data flowing down an arbitrarily deep tree — a theme, a locale.- Pinia is right when state needs to be both read and written from many places that aren't necessarily related by component nesting at all — a cart icon in the header and a checkout page deep in a different route both need the same cart state, and neither is an ancestor of the other.
Reaching for Pinia by default for every piece of state is a common overcorrection — plenty of state is genuinely local to one component and only adds indirection by living in a store. The next lesson covers actions and getters in more depth, including how to organize a larger store.