Pinia状态管理 #
一、Pinia简介 #
Pinia是Vue官方推荐的状态管理库,相比Vuex更加简洁、类型友好。
1.1 安装 #
bash
npm install pinia
1.2 基本配置 #
javascript
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.mount('#app')
1.3 Pinia vs Vuex #
| 特性 | Pinia | Vuex |
|---|---|---|
| 模块化 | 每个Store独立 | 需要modules |
| TypeScript | 完美支持 | 需要额外配置 |
| Mutations | 无 | 必须通过mutations修改 |
| 代码量 | 较少 | 较多 |
| Composition API | 原生支持 | 需要使用API |
二、定义Store #
2.1 Setup Store(推荐) #
javascript
// stores/counter.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCounterStore = defineStore('counter', () => {
// state
const count = ref(0)
// getters
const doubled = computed(() => count.value * 2)
// actions
function increment() {
count.value++
}
function decrement() {
count.value--
}
function $reset() {
count.value = 0
}
return {
count,
doubled,
increment,
decrement,
$reset
}
})
2.2 Options Store #
javascript
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
// state
state: () => ({
count: 0,
name: '计数器'
}),
// getters
getters: {
doubled: (state) => state.count * 2,
// 使用this访问其他getter
doubledPlusOne() {
return this.doubled + 1
}
},
// actions
actions: {
increment() {
this.count++
},
decrement() {
this.count--
},
async fetchCount() {
const response = await fetch('/api/count')
this.count = await response.json()
}
}
})
三、使用Store #
3.1 基本使用 #
vue
<template>
<div>
<p>计数: {{ counter.count }}</p>
<p>双倍: {{ counter.doubled }}</p>
<button @click="counter.increment">+1</button>
<button @click="counter.decrement">-1</button>
</div>
</template>
<script setup>
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
// 访问state
console.log(counter.count)
// 访问getter
console.log(counter.doubled)
// 调用action
counter.increment()
</script>
3.2 解构使用 #
vue
<script setup>
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
// ❌ 直接解构会丢失响应性
// const { count, doubled } = counter
// ✅ 使用storeToRefs解构state和getter
const { count, doubled } = storeToRefs(counter)
// ✅ actions可以直接解构
const { increment, decrement } = counter
</script>
3.3 修改State #
vue
<script setup>
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
// 直接修改
counter.count++
// $patch批量修改
counter.$patch({
count: 10,
name: '新名称'
})
// $patch函数形式
counter.$patch((state) => {
state.count++
state.name = '更新后的名称'
})
// $reset重置状态
counter.$reset()
// 替换整个state
counter.$state = {
count: 100,
name: '全新状态'
}
</script>
四、Getters #
4.1 定义Getters #
javascript
// stores/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
users: [
{ id: 1, name: '张三', age: 25 },
{ id: 2, name: '李四', age: 30 },
{ id: 3, name: '王五', age: 28 }
]
}),
getters: {
// 基本getter
userCount: (state) => state.users.length,
// 返回函数的getter
getUserById: (state) => {
return (id) => state.users.find(user => user.id === id)
},
// 访问其他getter
adultUsers(state) {
return state.users.filter(user => user.age >= 18)
},
adultCount() {
return this.adultUsers.length
}
}
})
4.2 使用Getters #
vue
<script setup>
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
// 访问getter
console.log(userStore.userCount)
console.log(userStore.adultUsers)
// 使用返回函数的getter
const user = userStore.getUserById(1)
</script>
五、Actions #
5.1 定义Actions #
javascript
// stores/product.js
import { defineStore } from 'pinia'
export const useProductStore = defineStore('product', {
state: () => ({
products: [],
loading: false,
error: null
}),
actions: {
async fetchProducts() {
this.loading = true
this.error = null
try {
const response = await fetch('/api/products')
this.products = await response.json()
} catch (e) {
this.error = e.message
} finally {
this.loading = false
}
},
async addProduct(product) {
const response = await fetch('/api/products', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(product)
})
const newProduct = await response.json()
this.products.push(newProduct)
},
async updateProduct(id, updates) {
const response = await fetch(`/api/products/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates)
})
const updatedProduct = await response.json()
const index = this.products.findIndex(p => p.id === id)
if (index > -1) {
this.products[index] = updatedProduct
}
},
async deleteProduct(id) {
await fetch(`/api/products/${id}`, { method: 'DELETE' })
this.products = this.products.filter(p => p.id !== id)
}
}
})
5.2 使用Actions #
vue
<script setup>
import { onMounted } from 'vue'
import { useProductStore } from '@/stores/product'
const productStore = useProductStore()
onMounted(() => {
productStore.fetchProducts()
})
async function handleAdd() {
await productStore.addProduct({
name: '新产品',
price: 99.9
})
}
</script>
六、组合式Store #
6.1 在Store中使用其他Store #
javascript
// stores/cart.js
import { defineStore } from 'pinia'
import { useUserStore } from './user'
export const useCartStore = defineStore('cart', {
state: () => ({
items: []
}),
getters: {
// 使用其他store的数据
cartWithUser(state) {
const userStore = useUserStore()
return {
items: state.items,
user: userStore.currentUser
}
}
},
actions: {
async checkout() {
const userStore = useUserStore()
if (!userStore.isLoggedIn) {
throw new Error('请先登录')
}
// 结账逻辑
}
}
})
6.2 组合式函数风格 #
javascript
// stores/auth.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useAuthStore = defineStore('auth', () => {
const token = ref(localStorage.getItem('token'))
const user = ref(null)
const isLoggedIn = computed(() => !!token.value)
async function login(credentials) {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials)
})
const data = await response.json()
token.value = data.token
user.value = data.user
localStorage.setItem('token', data.token)
}
function logout() {
token.value = null
user.value = null
localStorage.removeItem('token')
}
return {
token,
user,
isLoggedIn,
login,
logout
}
})
七、插件 #
7.1 创建插件 #
javascript
// plugins/piniaPersist.js
import { watch } from 'vue'
export function piniaPersist({ store }) {
// 从localStorage恢复状态
const savedState = localStorage.getItem(`pinia-${store.$id}`)
if (savedState) {
store.$patch(JSON.parse(savedState))
}
// 监听变化并保存
watch(
() => store.$state,
(state) => {
localStorage.setItem(`pinia-${store.$id}`, JSON.stringify(state))
},
{ deep: true }
)
}
7.2 使用插件 #
javascript
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { piniaPersist } from './plugins/piniaPersist'
import App from './App.vue'
const pinia = createPinia()
pinia.use(piniaPersist)
const app = createApp(App)
app.use(pinia)
app.mount('#app')
八、完整示例 #
javascript
// stores/todo.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useTodoStore = defineStore('todo', () => {
const todos = ref([])
const filter = ref('all')
const loading = ref(false)
const filteredTodos = computed(() => {
switch (filter.value) {
case 'active':
return todos.value.filter(t => !t.completed)
case 'completed':
return todos.value.filter(t => t.completed)
default:
return todos.value
}
})
const remaining = computed(() =>
todos.value.filter(t => !t.completed).length
)
async function fetchTodos() {
loading.value = true
try {
const response = await fetch('/api/todos')
todos.value = await response.json()
} finally {
loading.value = false
}
}
async function addTodo(text) {
const response = await fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text, completed: false })
})
const todo = await response.json()
todos.value.push(todo)
}
async function toggleTodo(id) {
const todo = todos.value.find(t => t.id === id)
if (todo) {
await fetch(`/api/todos/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ completed: !todo.completed })
})
todo.completed = !todo.completed
}
}
async function removeTodo(id) {
await fetch(`/api/todos/${id}`, { method: 'DELETE' })
todos.value = todos.value.filter(t => t.id !== id)
}
function setFilter(newFilter) {
filter.value = newFilter
}
return {
todos,
filter,
loading,
filteredTodos,
remaining,
fetchTodos,
addTodo,
toggleTodo,
removeTodo,
setFilter
}
})
九、总结 #
Pinia核心概念 #
| 概念 | 说明 |
|---|---|
| Store | 状态容器 |
| State | 状态数据 |
| Getters | 计算属性 |
| Actions | 方法 |
Pinia要点:
- 推荐使用Setup Store风格
- 使用storeToRefs解构保持响应性
- 直接修改state或使用$patch
- Actions支持异步操作
- 可以组合使用多个Store
- 支持插件扩展功能
最后更新:2026-03-26