从Vuex到Pinia的迁移逻辑
Vue3项目中的状态管理方案,Pinia已成为事实标准。相比Vuex,Pinia去掉了mutation层、简化了模块注册、内置TypeScript推断、支持Composition API写法,代码量减少约40%。迁移成本不高,核心思路是把Vuex的state/computed映射为Pinia的state/getters,把actions直接平移。
Pinia的Store定义方式有两种:Option API风格和Setup风格。Setup风格和Composition API一致,灵活度更高,推荐在新项目中优先使用。
Setup Store的标准写法
以用户认证Store为例:
import { defineStore } from "pinia"
import { ref, computed } from "vue"
import { loginApi, getUserInfoApi } from "@/api/auth"
export const useAuthStore = defineStore("auth", () => {
const token = ref(localStorage.getItem("token") || "")
const userInfo = ref(null)
const isLoggedIn = computed(() => !!token.value)
const userName = computed(() => userInfo.value?.name ?? "")
async function login(credentials) {
const res = await loginApi(credentials)
token.value = res.token
localStorage.setItem("token", res.token)
await fetchUserInfo()
}
async function fetchUserInfo() {
userInfo.value = await getUserInfoApi()
}
function logout() {
token.value = ""
userInfo.value = null
localStorage.removeItem("token")
}
return { token, userInfo, isLoggedIn, userName, login, fetchUserInfo, logout }
})
ref声明的变量即为state,computed即为getters,普通函数即为actions。结构清晰,无需额外记忆mutation/action区别。
Composable模式处理局部状态
并非所有状态都需要放进全局Store。组件级别的可复用逻辑用Composable封装更合适。一个常见的判断标准:如果状态只在两个以上组件间共享且跨路由持久化,放Pinia;如果只在同一页面的多个组件间共享,用Composable。
以表格分页逻辑为例,封装为Composable:
import { ref, computed, watch } from "vue"
export function usePagination(fetchFn, pageSize = 20) {
const currentPage = ref(1)
const total = ref(0)
const data = ref([])
const loading = ref(false)
const totalPages = computed(() => Math.ceil(total.value / pageSize))
async function loadData() {
loading.value = true
try {
const res = await fetchFn({ page: currentPage.value, size: pageSize })
data.value = res.list
total.value = res.total
} finally {
loading.value = false
}
}
function goToPage(page) {
if (page < 1 || page > totalPages.value) return
currentPage.value = page
}
watch(currentPage, loadData)
return { currentPage, total, data, loading, totalPages, loadData, goToPage }
}
组件中使用:
const { data, loading, currentPage, totalPages, goToPage } = usePagination(
(params) => api.getOrders(params),
20
)
Pinia插件扩展能力
Pinia的插件机制能实现跨切面逻辑。最典型的是持久化插件:
import { PiniaPluginContext } from "pinia"
function persistPlugin({ store }: PiniaPluginContext) {
const key = `pinia-${store.$id}`
const saved = localStorage.getItem(key)
if (saved) store.$patch(JSON.parse(saved))
store.$subscribe((_, state) => {
localStorage.setItem(key, JSON.stringify(state))
})
}
注册插件:
const pinia = createPinia()
pinia.use(persistPlugin)
也可以用现成的pinia-plugin-persistedstate包,功能更完善,支持sessionStorage和自定义序列化。
性能优化与常见陷阱
Pinia的store响应式基于Vue的reactive系统。一个常见性能问题是Store中存放了大型对象数组,每次修改触发组件全量重渲染。解决方案是用shallowRef替代ref存放大数据:
const bigList = shallowRef([])
// 更新时整体替换引用而非逐项修改
bigList.value = [...newList]
另一个陷阱是在Composable中直接解构Store的reactive属性。解构会丢失响应性,必须用storeToRefs:
const authStore = useAuthStore()
// 错误写法:isLoggedIn变成普通布尔值,失去响应性
const { isLoggedIn } = authStore
// 正确写法
const { isLoggedIn } = storeToRefs(authStore)
action可以直接解构,因为函数本身不需要响应式追踪:
const { login, logout } = authStore // 正确
Pinia配合Vue3 DevTools能查看Store的状态时间线,调试比Vuex方便不少。大型项目中建议按业务域拆分Store,每个Store职责单一,不要出现一个”global”Store包揽全部状态的情况。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-zu-he-shi-api-zhuang-tai-guan-li-pinia-yu-composable/