第一章:重新理解 Pinia 的本质
在 Vue 生态中,状态管理一直是重要的一环。从 Vuex 到 Pinia,Vue 官方的状态管理解决方案发生了显著的变化。但很多开发者只是停留在如何使用 Pinia 的层面,很少深入理解其设计思想和实现原理。
在本章中,我们将重新审视 Pinia 的本质,理解它为什么不是"Vuex 5",而是 Vue3 响应式系统的"第一公民"。
Pinia 不是 Vuex 5,它是 Vue3 响应式系统的"第一公民"
很多人初学 Pinia 时,会把它当作 Vuex 的升级版,认为它只是 Vuex 5 的另一种实现。但这种理解是错误的。Pinia 的设计哲学与 Vuex 有着根本的不同:
// Vuex 的方式 - 通过 mutations 修改状态
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
})
// Pinia 的方式 - 直接修改状态
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
function increment() {
count.value++
}
async function incrementAsync() {
setTimeout(() => {
increment()
}, 1000)
}
return { count, increment, incrementAsync }
})从上面的对比可以看出,Pinia 抛弃了 Vuex 中复杂的概念,如 Mutations、Modules 等,而是直接利用 Vue3 的响应式系统。这种方式更加直观,也更容易理解。
基于 reactive、ref、computed,无 Mutation/Module 的复杂抽象
Pinia 的核心就是 Vue3 的响应式 API。它没有重新发明轮子,而是直接利用了 Vue3 提供的 reactive、ref 和 computed:
// 简化版的 Pinia 实现原理
import { reactive, ref, computed } from 'vue'
function defineStore(id, setup) {
// 存储实例
const stores = new Map()
return function useStore() {
// 如果已经创建过,直接返回
if (stores.has(id)) {
return stores.get(id)
}
// 执行 setup 函数获取初始状态
const store = setup()
// 使用 reactive 包装整个 store
const reactiveStore = reactive(store)
// 保存实例
stores.set(id, reactiveStore)
return reactiveStore
}
}
// 使用示例
const useUserStore = defineStore('user', () => {
const user = reactive({
name: '',
age: 0
})
const isAdmin = computed(() => user.role === 'admin')
const updateName = (name) => {
user.name = name
}
return { user, isAdmin, updateName }
})这种设计使得 Pinia 更加轻量,也更容易与 Vue3 的其他特性集成。
Composition API + 响应式:状态即 reactive 对象,计算即 computed
Pinia 与 Vue3 的 Composition API 完美融合,使得状态管理变得更加自然:
// 在组件中使用
import { useCounterStore } from '@/stores/counter'
export default {
setup() {
// 直接获取 store 实例
const counter = useCounterStore()
// 可以直接解构,而不会失去响应性
const { count, increment } = storeToRefs(counter)
return {
count,
increment,
// 可以直接使用 store 上的方法
counter
}
}
}这种使用方式与 Composition API 的理念完全一致,开发者不需要学习额外的概念,只需要掌握 Vue3 的响应式系统即可。
TypeScript 优先:类型自动推导,无需冗余声明
Pinia 在设计之初就考虑了 TypeScript 的支持,提供了出色的类型推导能力:
import { defineStore } from 'pinia'
interface User {
id: number
name: string
email: string
}
export const useUserStore = defineStore('user', () => {
const user = ref<User | null>(null)
const isLoggedIn = computed(() => !!user.value)
async function login(credentials: { email: string; password: string }) {
// 登录逻辑
const userData = await api.login(credentials)
user.value = userData
return userData
}
function logout() {
user.value = null
}
return {
user,
isLoggedIn,
login,
logout
}
})
// 在组件中使用时,TypeScript 会自动推导出所有类型
const userStore = useUserStore()
// userStore.user 类型为 User | null
// userStore.login 参数类型为 { email: string; password: string }
// userStore.isLoggedIn 类型为 ComputedRef<boolean>这种自动类型推导大大减少了开发者需要编写的类型声明代码,同时保证了类型安全。
模块化设计:defineStore(id, () => {...}) 支持逻辑拆分
Pinia 通过 defineStore 函数实现模块化设计,每个 store 都是独立的:
// stores/user.js
export const useUserStore = defineStore('user', () => {
const user = ref(null)
async function login(credentials) {
// 登录逻辑
}
return { user, login }
})
// stores/cart.js
export const useCartStore = defineStore('cart', () => {
const items = ref([])
function addToCart(product) {
items.value.push(product)
}
const total = computed(() => {
return items.value.reduce((sum, item) => sum + item.price, 0)
})
return { items, addToCart, total }
})每个 store 都有唯一的 ID,可以在应用的任何地方通过 useStore() 函数获取其实例。
轻量核心:无 Mutation、无 Action Payload、无 Module 嵌套
Pinia 的设计哲学是"少即是多"。它移除了 Vuex 中很多复杂的概念:
- 无 Mutations - 直接修改状态,无需通过 commit
- 无 Action Payload - Actions 就是普通函数,参数传递更自然
- 无 Module 嵌套 - 通过组合多个 store 实现模块化
// Vuex 风格
store.commit('user/setRole', 'admin')
store.dispatch('cart/addToCart', { id: 1, name: 'Product' })
// Pinia 风格
userStore.setRole('admin')
cartStore.addToCart({ id: 1, name: 'Product' })这种简化不仅减少了学习成本,也减少了代码量,使得状态管理更加直观。
小结
在本章中,我们深入探讨了 Pinia 的本质和设计哲学:
- Pinia 是 Vue3 响应式系统的"第一公民",而不是 Vuex 的简单升级
- 它直接利用 Vue3 的响应式 API,无需重新发明轮子
- 与 Composition API 完美融合,使用方式更加自然
- 提供出色的 TypeScript 支持,类型自动推导
- 通过模块化设计支持逻辑拆分
- 移除了 Vuex 中复杂的概念,使 API 更加简洁
在下一章中,我们将深入探讨 Pinia 的响应式状态实现机制,包括 state 的 reactive 实现、$patch 批量更新原理等内容。
思考题:
- 你在项目中使用 Pinia 时,有没有遇到过与 Vuex 使用习惯冲突的情况?
- 你觉得 Pinia 移除 Mutations 的设计是好是坏?为什么?