Pinia vs Vuex 对比 #
概述 #
Pinia 和 Vuex 都是 Vue.js 的状态管理库。Pinia 作为 Vuex 的继任者,在设计上更加简洁和现代化。本节将详细对比两者的差异。
核心概念对比 #
结构对比 #
text
Vuex 结构
├── State ──────── 状态数据
├── Getters ────── 计算属性
├── Mutations ──── 同步修改(必须)
└── Actions ────── 异步操作
Pinia 结构
├── State ──────── 状态数据
├── Getters ────── 计算属性
└── Actions ────── 同步/异步操作(无 Mutations)
代码量对比 #
Vuex 示例 #
ts
// store/modules/counter.ts
import { Module } from 'vuex'
import { RootState } from '../index'
interface CounterState {
count: number
}
const counterModule: Module<CounterState, RootState> = {
namespaced: true,
state: () => ({
count: 0
}),
getters: {
doubleCount: (state) => state.count * 2
},
mutations: {
INCREMENT(state) {
state.count++
},
INCREMENT_BY(state, payload: number) {
state.count += payload
},
RESET(state) {
state.count = 0
}
},
actions: {
increment({ commit }) {
commit('INCREMENT')
},
incrementBy({ commit }, payload: number) {
commit('INCREMENT_BY', payload)
},
reset({ commit }) {
commit('RESET')
},
async fetchInitialValue({ commit }) {
const response = await fetch('/api/initial-value')
const value = await response.json()
commit('INCREMENT_BY', value)
}
}
}
export default counterModule
Pinia 示例 #
ts
// stores/counter.ts
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
getters: {
doubleCount: (state) => state.count * 2
},
actions: {
increment() {
this.count++
},
incrementBy(payload: number) {
this.count += payload
},
reset() {
this.count = 0
},
async fetchInitialValue() {
const response = await fetch('/api/initial-value')
const value = await response.json()
this.count += value
}
}
})
详细对比表 #
| 特性 | Pinia | Vuex |
|---|---|---|
| 核心概念 | State, Getters, Actions | State, Getters, Mutations, Actions |
| Mutations | 无 | 必须通过 Mutations 修改状态 |
| 模块化 | 每个 Store 独立 | 需要手动配置 modules |
| 命名空间 | 自动(Store ID) | 需要手动开启 namespaced |
| TypeScript | 原生支持,自动推断 | 需要额外配置 |
| 代码量 | 少 | 多 |
| 学习曲线 | 低 | 中 |
| DevTools | 完整支持 | 完整支持 |
| 包体积 | ~1KB | ~3KB |
| Vue 版本 | Vue 2 & 3 | Vue 2 & 3 |
| Composition API | 完美支持 | 需要额外 API |
| 热更新 | 简单 | 复杂 |
主要差异详解 #
1. Mutations 的移除 #
Vuex:必须通过 Mutations 修改状态
ts
// Vuex
mutations: {
SET_USER(state, user) {
state.user = user
}
},
actions: {
async login({ commit }, credentials) {
const user = await api.login(credentials)
commit('SET_USER', user)
}
}
Pinia:直接在 Actions 中修改
ts
// Pinia
actions: {
async login(credentials) {
this.user = await api.login(credentials)
}
}
2. 模块化 #
Vuex:需要手动配置嵌套模块
ts
// Vuex
const store = createStore({
modules: {
user: userModule,
cart: cartModule,
product: productModule
}
})
// 访问
this.$store.state.user.name
this.$store.dispatch('user/login')
Pinia:每个 Store 独立
ts
// Pinia
const userStore = useUserStore()
const cartStore = useCartStore()
const productStore = useProductStore()
// 访问
userStore.name
userStore.login()
3. TypeScript 支持 #
Vuex:需要大量类型定义
ts
// Vuex
import { createStore, Store } from 'vuex'
interface State {
count: number
}
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$store: Store<State>
}
}
const store = createStore<State>({
state: {
count: 0
}
})
Pinia:开箱即用的类型支持
ts
// Pinia
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0 // 自动推断为 number
})
})
4. Composition API 支持 #
Vuex:使用 useStore
ts
import { useStore } from 'vuex'
export default {
setup() {
const store = useStore()
const count = computed(() => store.state.count)
const doubleCount = computed(() => store.getters.doubleCount)
function increment() {
store.dispatch('increment')
}
return { count, doubleCount, increment }
}
}
Pinia:直接使用 Store
ts
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'
export default {
setup() {
const store = useCounterStore()
const { count, doubleCount } = storeToRefs(store)
const { increment } = store
return { count, doubleCount, increment }
}
}
5. Setup Store 风格 #
Pinia 独有:支持 Composition API 风格
ts
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
功能对比 #
状态持久化 #
Vuex:需要手动实现或使用第三方库
ts
// Vuex 持久化
const persistedState = localStorage.getItem('vuex')
const store = createStore({
state: persistedState ? JSON.parse(persistedState) : initialState,
plugins: [
store => {
store.subscribe((mutation, state) => {
localStorage.setItem('vuex', JSON.stringify(state))
})
}
]
})
Pinia:使用 pinia-plugin-persistedstate
ts
// Pinia 持久化
export const useUserStore = defineStore('user', {
state: () => ({ name: '', token: '' }),
persist: true
})
热更新 #
Vuex:复杂的 HMR 配置
ts
// Vuex HMR
if (import.meta.hot) {
import.meta.hot.accept(['./store/modules/user'], (newModules) => {
const newUserModule = newModules[0].default
store.hotUpdate({
modules: {
user: newUserModule
}
})
})
}
Pinia:简单的 HMR 配置
ts
// Pinia HMR
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useUserStore, import.meta.hot))
}
插件系统 #
Vuex:插件接收 store
ts
// Vuex 插件
const myPlugin = (store) => {
store.subscribe((mutation, state) => {
console.log(mutation.type)
})
}
Pinia:插件接收 context
ts
// Pinia 插件
const myPlugin = ({ store }) => {
store.$subscribe((mutation, state) => {
console.log(mutation.type)
})
}
迁移建议 #
何时选择 Pinia #
- 新项目
- 使用 Vue 3
- 需要 TypeScript 支持
- 希望更简洁的代码
- 使用 Composition API
何时继续使用 Vuex #
- 现有大型项目
- 团队熟悉 Vuex
- 需要严格的状态修改追踪
- 使用 Vue 2 且不需要迁移
迁移成本 #
低成本场景 #
- 小型项目
- 简单的状态管理
- 良好的 TypeScript 支持
高成本场景 #
- 大型项目
- 复杂的模块依赖
- 大量使用 Vuex 特有功能
总结 #
| 方面 | Pinia | Vuex |
|---|---|---|
| 推荐程度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| 新项目 | 推荐 | 不推荐 |
| 学习难度 | 低 | 中 |
| 代码简洁度 | 高 | 低 |
| TypeScript | 优秀 | 一般 |
| 维护成本 | 低 | 中 |
Pinia 作为 Vue 官方推荐的状态管理库,在各方面都优于 Vuex。对于新项目,强烈推荐使用 Pinia。
下一步 #
现在你已经了解了 Pinia 和 Vuex 的差异,接下来让我们学习如何从 Vuex 迁移到 Pinia。
- 迁移指南 - 从 Vuex 迁移到 Pinia
最后更新:2026-03-28