Pinia 把状态管理拆成三个清晰的角色:State(状态)、Getters(获取器)、Actions(动作)。它们各司其职,组合在一起就能覆盖从简单计数器到复杂业务逻辑的所有场景。相比 Vuex 的 Mutations + Actions 分离设计,Pinia 去掉了 Mutations,读写状态更直接,概念更少,写起来也更自然。
State:数据的家
State 就是存储数据的地方。在 Pinia 中,定义 State 就像在组件中定义 data,只不过它的数据是全局共享的——任何组件都可以访问和修改它。
定义方式(选项式 Store):
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
name: '',
isLoggedIn: false,
cartItems: []
})
})
组合式 Store 写法(更推荐):
import { ref, reactive } from 'vue'
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', () => {
const name = ref('')
const isLoggedIn = ref(false)
const cartItems = ref([])
return { name, isLoggedIn, cartItems }
})
在组件中使用:
const userStore = useUserStore()
console.log(userStore.name) // 读取状态
userStore.name = 'Alice' // 直接修改状态
userStore.$patch({ name: 'Bob' }) // 批量修改状态
关键点:
- State 默认就是响应式的,用
ref/reactive包裹,或选项式返回一个对象。 - 修改 State 不需要通过 mutations,直接赋值即可,Pinia 内部会自动触发依赖该状态的组件更新。
- 如果你需要一次修改多个属性,推荐使用
$patch,它能减少响应式通知次数,也方便 DevTools 追踪变更。
Getters:计算属性,带缓存
Getters 用来从 State 中派生数据,类似于组件里的 computed。它们的特点是有缓存,只有当依赖的 State 改变时才会重新计算。
定义方式:
export const useCartStore = defineStore('cart', () => {
const items = ref([])
// Getter:计算总价
const totalPrice = computed(() => {
return items.value.reduce((sum, item) => sum + item.price * item.qty, 0)
})
// Getter 可以依赖其他 Getter
const discountPrice = computed(() => {
return totalPrice.value > 100 ? totalPrice.value * 0.9 : totalPrice.value
})
return { items, totalPrice, discountPrice }
})
在组件中使用:
const cartStore = useCartStore()
console.log(cartStore.totalPrice) // 像访问普通属性一样使用
实用建议:
- 如果一个数据可以从 State 算出来,就把它放进 Getter,而不是存在 State 里重复维护。
- Getter 可以接受参数,但必须返回一个函数(注意这样会失去缓存能力,每次调用都会重新执行)。
- Getter 在 DevTools 中会以独立节点展示,便于调试。
Actions:业务逻辑的载体
Actions 是定义在 Store 中的方法,用来封装业务逻辑、修改 State、调用接口等。它们是 Pinia 中改变 State 的主要途径(尤其当修改需要经过一些异步步骤或多步骤处理时)。
定义方式:
export const useUserStore = defineStore('user', () => {
const name = ref('')
const isLoggedIn = ref(false)
// 同步 Action
function login(username) {
name.value = username
isLoggedIn.value = true
}
// 异步 Action
async function fetchProfile() {
try {
const res = await api.getProfile()
name.value = res.name
isLoggedIn.value = true
} catch (err) {
console.error('获取用户信息失败', err)
}
}
function logout() {
name.value = ''
isLoggedIn.value = false
}
return { name, isLoggedIn, login, logout, fetchProfile }
})
在组件中使用:
const userStore = useUserStore()
userStore.login('Alice') // 调用同步 Action
await userStore.fetchProfile() // 调用异步 Action
为什么推荐用 Actions 修改 State:
- 逻辑集中:把“登录”这种完整操作封到一个函数,组件只需调用它,不必关心内部改了哪些状态。
- 方便追踪:DevTools 能记录 Action 的调用时机、参数和状态变更快照,直接赋值虽然也能用,但调试体验不如 Actions。
- 异步支持天然:Actions 里
async/await用起来毫无负担,不需要像 Vuex 一样专门区分 Mutations 和 Actions。 - 可复用:Store 之间可以互相调用 Actions,比如
useCartStore().clearCart()在结算完成后由订单 Store 调用。
三者关系一言蔽之
- State 是“仓库里有什么”。
- Getters 是“我想怎么看这些货”(汇总、过滤、排序)。
- Actions 是“怎么入库、出库、盘点”(业务操作,可同步可异步)。
这种分层让状态管理变得清晰:组件只管触发 Actions 和读取 Getters,State 的维护细节藏在 Store 内部。需要加个“打印日志”或“失败重试”,改 Actions 就行,完全不影响组件。