博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Vuex与Pinia对比指南

Posted on 2026-08-19 22:58  混凝土方移位工程师  阅读(15)  评论(0)    收藏  举报

Vuex 与 Pinia 对比指南

本文总结 Vuex 和 Pinia 的核心区别、各自完整用法、以及模块化(modules)写法,供开发参考。


一、核心区别

Vuex Pinia
适用 Vue 2(也支持 Vue 3,但已进入维护模式) Vue 3(官方推荐)
修改数据 mutations(同步)+ actions(异步)必须分开 只有 actions,直接改,无 mutations
模块化 手动 module + namespaced: true 天然模块化,无需配置
TypeScript 支持差,类型要额外处理 原生支持,类型推断好
使用方式 this.$store + mapState/mapGetters/mapActions/mapMutations useXxxStore() 直接调用
体积 较大 更轻量

二、Vuex 使用方法

1. 安装

npm install vuex   # Vue 2 用 vuex@3,Vue 3 用 vuex@4

2. 创建 store

// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {                 // 数据
    count: 0,
    userInfo: {}
  },
  mutations: {             // 同步修改(唯一能改 state 的地方)
    INCREMENT(state) { state.count++ },
    SET_USER(state, user) { state.userInfo = user }
  },
  actions: {               // 异步操作,内部再 commit
    asyncIncrement({ commit }) {
      setTimeout(() => commit('INCREMENT'), 1000)
    }
  },
  getters: {               // 计算属性
    doubleCount: state => state.count * 2
  }
})

3. 注册

// main.js
import store from './store'
new Vue({ store, render: h => h(App) }).$mount('#app')

4. 组件里使用

// 直接访问
this.$store.state.count
this.$store.commit('INCREMENT')        // 同步修改
this.$store.dispatch('asyncIncrement')  // 异步操作
this.$store.getters.doubleCount

// 或用辅助函数(配合 computed / methods)
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex'

computed: {
  ...mapState(['count']),
  ...mapGetters(['doubleCount'])
}
methods: {
  ...mapActions(['asyncIncrement']),
  ...mapMutations(['INCREMENT'])
}

三、Pinia 使用方法

1. 安装

npm install pinia

2. 创建 store

Setup 风格(推荐,类似组合式 API):

// store/user.ts
import { defineStore } from 'pinia'
import { ref } from 'vue'

export const useUserStore = defineStore('user', () => {
  const token = ref('')
  const setToken = (val: string) => { token.value = val }
  return { token, setToken }
})

Options 风格(类似 Vuex,带 state/getters/actions):

export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  getters: {
    doubleCount: (state) => state.count * 2
  },
  actions: {
    increment() { this.count++ }
  }
})

3. 注册

// main.ts
import { createPinia } from 'pinia'

const app = createApp(App)
app.use(createPinia())
app.mount('#app')

4. 组件里使用

<script setup lang="ts">
import { useUserStore } from '@/store/user'

const userStore = useUserStore()
userStore.setToken('abc')        // 调用 action
console.log(userStore.token)     // 读 state
console.log(userStore.doubleCount) // 读 getter
</script>

四、模块化(modules)

项目变大后,按功能拆分模块,每个模块管自己的数据。

Vuex 的模块化(复杂,要配 namespaced)

目录结构:

store/
├── index.js          # 根 store,组装所有模块
└── modules/
    ├── user.js       # 用户模块
    └── cart.js       # 购物车模块

写一个模块 store/modules/user.js:

export default {
  namespaced: true,   // ⚠️ 必须写,否则 mutations/actions 会全局冲突

  state: {
    token: '',
    userInfo: null
  },
  mutations: {
    SET_TOKEN(state, token) {
      state.token = token
    }
  },
  actions: {
    async login({ commit }, form) {
      const token = await api.login(form)
      commit('SET_TOKEN', token)
    }
  },
  getters: {
    isLogin: state => !!state.token
  }
}

根 store 组装 store/index.js:

import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import cart from './modules/cart'

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    user,   // 注册模块
    cart
  }
})

组件里访问(带模块前缀):

// 直接访问:要加模块名前缀
this.$store.state.user.token               // state
this.$store.commit('user/SET_TOKEN', 'x')  // mutation 要 user/ 前缀
this.$store.dispatch('user/login', form)   // action 要 user/ 前缀
this.$store.getters['user/isLogin']        // getter 要 user/ 前缀

// 辅助函数:第一个参数传模块名
import { mapState, mapActions } from 'vuex'
computed: { ...mapState('user', ['token', 'userInfo']) }
methods: { ...mapActions('user', ['login']) }

Pinia 的模块化(简单,天然隔离)

目录结构:

store/
├── index.ts          # (可选)创建 pinia 实例
└── modules/
    ├── user.ts       # 用户模块
    └── cart.ts       # 购物车模块

写一个模块 store/modules/user.ts:

import { defineStore } from 'pinia'
import { ref } from 'vue'

export const useUserStore = defineStore('user', () => {
  const token = ref('')
  const userInfo = ref(null)

  const setToken = (val: string) => { token.value = val }
  const login = async (form: any) => {
    const res = await api.login(form)
    setToken(res.token)
  }

  return { token, userInfo, setToken, login }
})

每个 defineStore('名字', ...) 的「名字」就是模块名,天然隔离,不需要 namespaced

组件里访问(直接 use 对应 store):

import { useUserStore } from '@/store/modules/user'
import { useCartStore } from '@/store/modules/cart'

const userStore = useUserStore()   // 拿到用户模块
const cartStore = useCartStore()   // 拿到购物车模块

userStore.token          // 读,没有前缀
userStore.setToken('x')  // 调 action,没有前缀
userStore.login(form)

模块间互相调用对比

Vuex(要带命名空间前缀 + root):

// user 模块里调用 cart 模块的 action
dispatch('cart/addItem', item, { root: true })

Pinia(直接 use 另一个 store):

// user 模块里调用 cart 模块
const cartStore = useCartStore()
cartStore.addItem(item)

五、模块化对比总结

Vuex Pinia
模块怎么定义 modules: { user, cart } 手动注册 defineStore('user', ...) 定义即模块
命名空间 必须写 namespaced: true,否则冲突 无此概念,天然隔离
访问前缀 this.$store.state.user.xxxuser/xxx 直接 userStore.xxx
一个模块一个文件 ✅ 是 ✅ 是
模块间互相调用 dispatch('other/xxx', data, { root: true }) useOtherStore().xxx()

六、最直观的写法对比

操作 Vuex Pinia
同步改数据 commit('INCREMENT') store.increment() 直接调
异步改数据 dispatch('asyncIncrement') store.asyncIncrement()(同一个方法)
读数据 this.$store.state.count store.count
计算属性 getters + mapGetters getters 或直接 computed

七、选型建议

  • Vue 3 新项目:直接用 Pinia,不用犹豫。
  • Vue 2 老项目:继续用 Vuex,没必要迁移。

概念对应关系:statestategettersgettersactionsactions,唯一消失的是 Vuex 的 mutations(Pinia 合并进了 actions)。


八、一句话记忆

Pinia 就是「删掉 mutations、去掉 module/namespaced 复杂度、TS 更好」的 Vuex 简化版。
模块化上,Pinia 是「每个 store 一个文件,用时直接 import 对应的 useXxxStore」,没有 Vuex 那一堆命名空间的概念。