263 lines
6.2 KiB
Markdown
263 lines
6.2 KiB
Markdown
---
|
|
title: Use Pinia for Large-Scale Vue Applications
|
|
impact: MEDIUM
|
|
impactDescription: Hand-rolled reactive stores lack DevTools integration, TypeScript support, and debugging capabilities needed for production apps
|
|
type: best-practice
|
|
tags: [vue3, pinia, state-management, devtools, architecture, scalability]
|
|
---
|
|
|
|
# Use Pinia for Large-Scale Vue Applications
|
|
|
|
**Impact: MEDIUM** - While Vue's Composition API allows creating simple reactive stores with `reactive()` and `ref()`, these hand-rolled solutions lack the tooling, conventions, and debugging capabilities needed for large-scale production applications. Pinia is the official Vue state management solution and should be used for any non-trivial application.
|
|
|
|
## Task Checklist
|
|
|
|
- [ ] Use Pinia for applications with shared state across multiple components
|
|
- [ ] Use Pinia when team collaboration requires consistent patterns
|
|
- [ ] Use Pinia when you need Vue DevTools state debugging
|
|
- [ ] Hand-rolled `reactive()` is acceptable only for simple, single-developer apps
|
|
- [ ] Migrate from Vuex to Pinia (Vuex is in maintenance mode)
|
|
|
|
## When Hand-Rolled State is Acceptable
|
|
|
|
Simple reactive state is fine for:
|
|
- Prototypes and proof-of-concepts
|
|
- Very small applications with minimal shared state
|
|
- Single-developer projects with limited scope
|
|
- Learning purposes
|
|
|
|
```javascript
|
|
// Simple hand-rolled store - OK for small apps
|
|
import { reactive, readonly } from 'vue'
|
|
|
|
export const store = reactive({
|
|
count: 0,
|
|
increment() {
|
|
this.count++
|
|
}
|
|
})
|
|
```
|
|
|
|
## When to Use Pinia
|
|
|
|
Use Pinia when you have any of these requirements:
|
|
|
|
### 1. DevTools Integration
|
|
|
|
Pinia provides rich Vue DevTools support:
|
|
- Timeline of state changes
|
|
- State inspection and editing
|
|
- Time-travel debugging
|
|
- Action tracking
|
|
|
|
```javascript
|
|
// Pinia store - fully visible in DevTools
|
|
import { defineStore } from 'pinia'
|
|
|
|
export const useCounterStore = defineStore('counter', {
|
|
state: () => ({ count: 0 }),
|
|
actions: {
|
|
increment() {
|
|
this.count++ // Tracked in DevTools timeline
|
|
}
|
|
}
|
|
})
|
|
```
|
|
|
|
### 2. TypeScript Support
|
|
|
|
Pinia has excellent TypeScript inference:
|
|
|
|
```typescript
|
|
// Full type inference without extra configuration
|
|
import { defineStore } from 'pinia'
|
|
|
|
export const useUserStore = defineStore('user', {
|
|
state: () => ({
|
|
name: '',
|
|
age: 0,
|
|
preferences: {
|
|
theme: 'light' as 'light' | 'dark'
|
|
}
|
|
}),
|
|
|
|
getters: {
|
|
// Return type is inferred
|
|
displayName: (state) => state.name || 'Anonymous'
|
|
},
|
|
|
|
actions: {
|
|
// Full parameter type checking
|
|
setUser(name: string, age: number) {
|
|
this.name = name
|
|
this.age = age
|
|
}
|
|
}
|
|
})
|
|
|
|
// Usage is fully typed
|
|
const userStore = useUserStore()
|
|
userStore.name // string
|
|
userStore.displayName // string
|
|
userStore.setUser('John', 30) // Type-checked
|
|
```
|
|
|
|
### 3. Team Collaboration
|
|
|
|
Pinia enforces conventions that help teams:
|
|
|
|
```javascript
|
|
// Consistent structure across all stores
|
|
// stores/products.js
|
|
export const useProductsStore = defineStore('products', {
|
|
state: () => ({ /* ... */ }),
|
|
getters: { /* ... */ },
|
|
actions: { /* ... */ }
|
|
})
|
|
|
|
// stores/cart.js - Same structure
|
|
export const useCartStore = defineStore('cart', {
|
|
state: () => ({ /* ... */ }),
|
|
getters: { /* ... */ },
|
|
actions: { /* ... */ }
|
|
})
|
|
```
|
|
|
|
### 4. Hot Module Replacement (HMR)
|
|
|
|
Pinia supports HMR out of the box - state persists during development:
|
|
|
|
```javascript
|
|
// State survives code changes during development
|
|
// No need to re-login or re-create state after every edit
|
|
```
|
|
|
|
### 5. Server-Side Rendering (SSR)
|
|
|
|
Pinia handles SSR state correctly:
|
|
|
|
```javascript
|
|
// Automatic per-request state isolation
|
|
// State serialization for hydration
|
|
// No cross-request pollution
|
|
```
|
|
|
|
### 6. Plugin Ecosystem
|
|
|
|
Pinia supports plugins for common needs:
|
|
|
|
```javascript
|
|
import { createPinia } from 'pinia'
|
|
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
|
|
|
const pinia = createPinia()
|
|
pinia.use(piniaPluginPersistedstate)
|
|
|
|
// Now stores can persist to localStorage
|
|
export const useSettingsStore = defineStore('settings', {
|
|
state: () => ({
|
|
theme: 'light',
|
|
language: 'en'
|
|
}),
|
|
persist: true // Automatically saved/restored
|
|
})
|
|
```
|
|
|
|
## Pinia vs Hand-Rolled Comparison
|
|
|
|
| Feature | Hand-Rolled `reactive()` | Pinia |
|
|
|---------|-------------------------|-------|
|
|
| DevTools integration | No | Yes |
|
|
| TypeScript inference | Manual | Automatic |
|
|
| HMR support | No | Yes |
|
|
| SSR support | Manual | Built-in |
|
|
| Plugins | No | Yes |
|
|
| Time-travel debugging | No | Yes |
|
|
| Learning curve | Lower | Slightly higher |
|
|
| Bundle size | Smaller | ~1KB |
|
|
| Team conventions | None | Enforced |
|
|
|
|
## Migration from Vuex
|
|
|
|
Vuex is now in maintenance mode. Migrate to Pinia for new features:
|
|
|
|
```javascript
|
|
// Vuex (legacy)
|
|
export default createStore({
|
|
state: { count: 0 },
|
|
mutations: {
|
|
INCREMENT(state) { state.count++ }
|
|
},
|
|
actions: {
|
|
increment({ commit }) { commit('INCREMENT') }
|
|
}
|
|
})
|
|
|
|
// Pinia (recommended)
|
|
export const useCounterStore = defineStore('counter', {
|
|
state: () => ({ count: 0 }),
|
|
actions: {
|
|
increment() { this.count++ } // No mutations needed!
|
|
}
|
|
})
|
|
```
|
|
|
|
**Pinia advantages over Vuex:**
|
|
- No mutations (simpler mental model)
|
|
- Better TypeScript support
|
|
- No nested modules complexity
|
|
- Smaller bundle size
|
|
- Composition API style available
|
|
|
|
## Pinia Store Styles
|
|
|
|
Choose the style that fits your team:
|
|
|
|
### Options Style (Similar to Options API)
|
|
|
|
```javascript
|
|
export const useCounterStore = defineStore('counter', {
|
|
state: () => ({ count: 0 }),
|
|
getters: {
|
|
double: (state) => state.count * 2
|
|
},
|
|
actions: {
|
|
increment() { this.count++ }
|
|
}
|
|
})
|
|
```
|
|
|
|
### Setup Style (Composition API)
|
|
|
|
```javascript
|
|
export const useCounterStore = defineStore('counter', () => {
|
|
const count = ref(0)
|
|
const double = computed(() => count.value * 2)
|
|
function increment() { count.value++ }
|
|
|
|
return { count, double, increment }
|
|
})
|
|
```
|
|
|
|
## Quick Start
|
|
|
|
```bash
|
|
npm install pinia
|
|
```
|
|
|
|
```javascript
|
|
// main.js
|
|
import { createApp } from 'vue'
|
|
import { createPinia } from 'pinia'
|
|
import App from './App.vue'
|
|
|
|
const app = createApp(App)
|
|
app.use(createPinia())
|
|
app.mount('#app')
|
|
```
|
|
|
|
## Reference
|
|
- [Vue.js - State Management](https://vuejs.org/guide/scaling-up/state-management.html)
|
|
- [Pinia Documentation](https://pinia.vuejs.org/)
|
|
- [Pinia vs Vuex](https://pinia.vuejs.org/introduction.html#comparison-with-vuex)
|