In Vue3, state management is an unavoidable topic.
Pinia is a lightweight state management library for Vue.js that allows you to share and manage state between components. We can think of Pinia as a global data warehouse where all components can retrieve or update data.
This chapter introduces Pinia, the officially recommended state management library for Vue3. Compared to Vuex, Pinia provides a more concise state management solution that aligns with the Vue3 Composition API mindset.
The following diagram compares the structural differences between the two:
* Vuex uses a tree structure with a single store and multi-level modules, with fixed hierarchy, relying on mutations, and is overall heavier.
* Pinia consists of multiple independent stores, is flat, lightweight, without modules, and has no namespace burden.
!(#)
**Pinia Core Features:**
* Supports Vue2 and Vue3
* Minimal API design
* Full TypeScript support
* Supports Composition API
* Modular design without nested modules
Pinia's flat structure allows components to directly connect to any store, getting what they need without going through tree-like modules or namespace paths. State flow is simple, direct, and has low coupling.
!(#)
* * *
## Installation and Configuration
Create a project with Vite:
npm create vite@latest vue-pinia-demo --template vue cd vue-pinia-demo npm install
### Install Pinia
First, install Pinia in your Vue3 project:
npm install pinia # or yarn add pinia
Pinia has three parts:
* state: stores data
* getters: computes derived data
* actions: executes logic, modifies state
### Configure Pinia
Configure Pinia in src/main.js or src/main.ts:
## Example
// main.js
import{ createApp } from 'vue'
import{ createPinia } from 'pinia'
import App from './App.vue'
// Create Pinia instance
const pinia = createPinia()
// Create Vue app
const app = createApp(App)
// Use Pinia
app.use(pinia)
app.mount('#app')
* * *
## Creating the First Store
A Store is the data warehouse in Pinia. Let's create a simple counter Store.
### Define Store
Create src/stores/useCounter.js:
## Example
// stores/useCounter.js
import{ defineStore } from 'pinia'
// Use defineStore to define store
// First parameter is the unique ID of the store
// Second parameter is the store's configuration options
export const useCounterStore = defineStore('counter',{
// state: defines the store's state data
state:()=>({
count:0,
name:'My Counter'
}),
// getters: defines computed properties based on state
getters:{
doubleCount:(state)=> state.count*2,
// Use this to access other getters
doubleCountPlusOne(){
return this.doubleCount+1
}
},
// actions: defines methods to modify state
actions:{
increment(){
this.count++
},
decrement(){
this.count--
},
// Can receive parameters
incrementBy(amount){
this.count+= amount
},
// Async action
async incrementAsync(){
// Simulate async operation
await new Promise(resolve => setTimeout(resolve,1000))
this.count++
}
}
})
### Store Structure Explanation
Let's understand the parts of a Store through a table:
| Part | Function | Example |
| --- | --- | --- |
| **state** | Defines stored data | `count: 0` |
| **getters** | Computed properties based on state | `doubleCount: state => state.count * 2` |
| **actions** | Methods to modify state | `increment() { this.count++ }` |
* * *
## Using Store in Components
### Basic Usage
The component code for src/components/CounterComponent.vue is as follows:
## Example
{{ store.name }}
Current count: {{ store.count }}
Double count: {{ store.doubleCount }}
Double plus one: {{ store.doubleCountPlusOne }}
import { useCounterStore } from '../stores/useCounter'
// Use store in setup
const store = useCounterStore()
// Method to reset state
function reset() {
store.$reset() // $reset method can reset state to initial value
}
Modify src/App.vue code as follows:
## Example
import CounterComponent from './components/CounterComponent.vue'
The entire project structure:
!(#)
Execute the npm run dev command, and visit **http://localhost:5173/** in the browser to see the effect:
!(#)
### Reactive Destructuring
If you want to directly use state properties in the template, you can use `storeToRefs`:
## Example
Count: {{ count }}
Name: {{ name }}
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
// Use storeToRefs to maintain reactivity
const { count, name } = storeToRefs(store)
// Note: Direct destructuring will lose reactivity!
// Wrong way: const { count, name } = store
* * *
## Composition API Style Store
Pinia also supports defining Stores using the Composition API style:
## Example
// stores/user.js
import{ defineStore } from 'pinia'
import{ ref, computed } from 'vue'
export const useUserStore = defineStore('user',()=>{
// state
const user = ref(null)
const isLoggedIn = ref(false)
// getters
const userName = computed(()=> user.value?.name||'Guest')
const userAge = computed(()=> user.value?.age||0)
// actions
function login(userData){
user.value= userData
isLoggedIn.value=true
}
function logout(){
user.value=null
isLoggedIn.value=false
}
return{
user,
isLoggedIn,
userName,
userAge,
login,
logout
}
})
* * *
## Store Interaction
Multiple Stores can call each other:
## Example
// stores/cart.js
import{ defineStore } from 'pinia'
export const useCartStore = defineStore('cart',{
state:()=>({
items:[]
}),
actions:{
addItem(product){
this.items.push(product)
// Call other store's action
const userStore = useUserStore()
if(userStore.isLoggedIn){
// Sync to user cart
this.syncToUserCart()
}
}
}
})
* * *
## Practical Example: Shopping Cart
Let's create a complete shopping cart example:
## Example
// stores/products.js
export const useProductStore = defineStore('products',{
state:()=>({
products:[
{ id:1, name:'Laptop', price:5999, stock:10},
{ id:2, name:'Smartphone', price:3999, stock:20},
{ id:3, name:'Wireless Earphones', price:299, stock:50}
]
}),
getters:{
// Get product by ID
getProductById:(state)=>(id)=>{
return state.products.find(product => product.id=== id)
},
// Get products in stock
availableProducts:(state)=>{
return state.products.filter(product => product.stock>0)
}
}
})
## Example
// stores/cart.js
export const useCartStore = defineStore('cart',{
state:()=>({
items:[],// { productId, quantity }
discount:0
}),
getters:{
// Total number of items in cart
totalItems:(state)=>{
return state.items.reduce((total, item)=> total + item.quantity,0)
},
// Total amount in cart
totalPrice:(state)=>{
const productStore = useProductStore()
return state.items.reduce((total, item)=>{
const product = productStore.getProductById(item.productId)
return total +(product?.price||0)* item.quantity
},0)
},
// Final price after discount
finalPrice:(state)=>{
return state.totalPrice*(1- state.discount/100)
}
},
actions:{
// Add product to cart
addToCart(productId, quantity =1){
const existingItem =this.items.find(item => item.productId=== productId)
if(existingItem){
existingItem.quantity+= quantity
}else{
this.items.push({ productId, quantity })
}
},
// Remove product from cart
removeFromCart(productId){
this.items=this.items.filter(item => item.productId!== productId)
},
// Clear cart
clearCart(){
this.items=[]
this.discount=0
},
// Set discount
setDiscount(percent){
this.discount=Math.max(0,Math.min(100, percent))
}
}
})
### Using Shopping Cart in Components
## Example