Skip to content

Vuex与Pinia:Vue状态管理的两种范式

在Vue应用中,当组件层级较深或跨组件共享数据时,单纯依靠propsemit 会导致数据流混乱。状态管理工具(Vuex/Pinia)应运而生,它们将共享状态集中管理,确保数据变化可追踪、可预测。本文将系统解析Vuex和Pinia的核心概念、使用方式及最佳实践,帮助你在项目中做出合适的选择。

一、Vuex:经典的状态管理方案

Vuex是Vue官方推出的第一代状态管理库,基于“单向数据流”设计,核心是“集中式存储管理应用的所有组件的状态”。其设计思想受Flux、Redux启发,强调“规则化修改状态”。

1. 核心模块及作用

Vuex的核心由5个部分组成,彼此分工明确,形成完整的状态管理闭环:

(1)State:状态容器

State是存储所有共享状态的“仓库”,本质是一个响应式对象。Vuex遵循“单一状态树”原则,整个应用的状态集中在一个State 中,便于调试和维护。

定义State

javascript
// store/index.js(Vuex 3.x,适用于Vue2)
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        // 定义状态:用户信息
        user: {name: '张三', role: 'user'},
        // 定义状态:商品列表
        products: []
    }
})

组件中访问State

vue
<!-- 方式1:直接访问 -->
<template>
  <p>{{ $store.state.user.name }}</p>
</template>

<!-- 方式2:mapState辅助函数(简化访问) -->
<script>
  import {mapState} from 'vuex'

  export default {
    computed: {
      // 映射this.user为$store.state.user
      ...mapState(['user'])
    }
  }
</script>

(2)Mutation:同步修改状态的唯一方式

Mutation是修改State的“唯一入口”,且必须是同步函数。它的作用是记录每一次状态变化,便于Vuex DevTools追踪状态变更历史。

定义Mutation

javascript
// store/index.js
export default new Vuex.Store({
    state: {count: 0},
    mutations: {
        // 定义mutation:第一个参数是state,第二个是载荷(payload)
        increment(state, payload) {
            state.count += payload || 1
        },
        decrement(state) {
            state.count--
        }
    }
})

触发Mutation

vue

<script>
  export default {
    methods: {
      handleClick() {
        // 必须通过commit触发mutation,不能直接修改state
        this.$store.commit('increment', 2) // 带载荷:加2
        this.$store.commit('decrement')
      }
    }
  }
</script>

为什么必须用Mutation?
直接修改state(如this.$store.state.count++)不会被DevTools记录,难以调试;而mutation通过“显式提交”的方式,确保每一次状态变化都可追踪。

(3)Action:处理异步操作

Action用于处理异步逻辑(如API请求),最终通过触发Mutation修改状态。它可以包含任意异步操作(定时器、请求等),但不能直接修改 State

定义Action

javascript
// store/index.js
export default new Vuex.Store({
    state: {products: []},
    mutations: {
        setProducts(state, products) {
            state.products = products
        }
    },
    actions: {
        // 定义action:第一个参数是context(包含commit、state等)
        fetchProducts(context) {
            // 模拟API请求
            return fetch('/api/products')
                .then(res => res.json())
                .then(data => {
                    // 异步操作完成后,通过commit触发mutation
                    context.commit('setProducts', data)
                })
        }
    }
})

触发Action

vue

<script>
  export default {
    mounted() {
      // 通过dispatch触发action
      this.$store.dispatch('fetchProducts')
    }
  }
</script>

(4)Getter:状态的计算属性

Getter用于从State中派生出新状态(类似组件的计算属性),并具有缓存机制——只有依赖的State变化时才会重新计算。

定义Getter

javascript
// store/index.js
export default new Vuex.Store({
    state: {products: [{id: 1, name: 'Vue', price: 50}, {id: 2, name: 'React', price: 60}]},
    getters: {
        // 过滤价格大于55的商品
        expensiveProducts(state) {
            return state.products.filter(p => p.price > 55)
        },
        // 带参数的getter(返回函数)
        productById(state) {
            return (id) => state.products.find(p => p.id === id)
        }
    }
})

使用Getter

vue

<template>
  <div>
    <p>高价商品:{{ $store.getters.expensiveProducts }}</p>
    <p>ID=1的商品:{{ $store.getters.productById(1) }}</p>
  </div>
</template>

(5)Module:模块化拆分

当应用状态复杂时,Module可将Store拆分为多个子模块(每个模块包含自己的statemutationactiongetter),避免单一 Store过于臃肿。

定义Module

javascript
// store/modules/user.js(用户模块)
export default {
    // 开启命名空间:避免模块间命名冲突
    namespaced: true,
    state: {name: '张三'},
    mutations: {
        updateName(state, newName) {
            state.name = newName
        }
    }
}

// store/modules/cart.js(购物车模块)
export default {
    namespaced: true,
    state: {items: []},
    mutations: {
        addItem(state, item) {
            state.items.push(item)
        }
    }
}

// store/index.js(组合模块)
import user from './modules/user'
import cart from './modules/cart'

export default new Vuex.Store({
    modules: {
        user, // 挂载用户模块
        cart  // 挂载购物车模块
    }
})

访问模块状态

vue

<template>
  <div>
    <!-- 访问模块state -->
    <p>用户名:{{ $store.state.user.name }}</p>

    <!-- 触发模块mutation(需带命名空间) -->
    <button @click="$store.commit('user/updateName', '李四')">修改用户名</button>
  </div>
</template>

2. Vuex 4.x(适用于Vue3)的变化

Vuex 4.x基本沿用3.x的API,主要适配Vue3的Composition API:

javascript
// Vue3中使用Vuex 4.x
import {useStore} from 'vuex'

export default {
    setup() {
        const store = useStore()
        // 访问state
        console.log(store.state.user.name)
        // 触发action
        store.dispatch('fetchProducts')
        return {store}
    }
}

二、Pinia:Vuex的继任者

Pinia是Vue官方推荐的新一代状态管理库,由Vuex核心团队成员开发。它简化了Vuex的API,天然支持TypeScript,且更贴合Vue3的Composition API。

1. 核心概念及使用方式

Pinia的设计更简洁,移除了Vuex中的MutationModule(模块由Store本身替代),核心概念只有3个:StoreStateActionGetter(是的,比Vuex少了Mutation)。

(1)Store:独立的状态容器

Pinia中没有“单一状态树”,而是通过defineStore创建多个独立的Store,每个Store就是一个模块,替代了Vuex的Module

定义Store

javascript
// store/user.js(用户Store)
import {defineStore} from 'pinia'

// 第一个参数:Store的唯一ID(必须)
// 第二个参数:Store配置
export const useUserStore = defineStore('user', {
    // State:函数返回初始状态(类似Vue组件的data)
    state: () => ({
        name: '张三',
        role: 'user'
    }),

    // Getter:类似计算属性(支持缓存)
    getters: {
        // getter接收state作为参数
        isAdmin: (state) => state.role === 'admin'
    },

    // Action:处理同步/异步操作(直接修改state)
    actions: {
        // 同步action
        updateName(newName) {
            this.name = newName // this指向state
        },
        // 异步action
        async fetchUserInfo() {
            const res = await fetch('/api/user')
            const data = await res.json()
            this.name = data.name // 直接修改state
            this.role = data.role
        }
    }
})

(2)State:响应式状态

Pinia的State定义与Vue组件的data类似,通过函数返回初始状态,且天然是响应式的。

访问和修改State

vue

<template>
  <div>
    <p>用户名:{{ userStore.name }}</p>
    <button @click="handleUpdate">修改用户名</button>
  </div>
</template>

<script setup>
  // 导入并使用Store
  import {useUserStore} from './store/user'

  const userStore = useUserStore()

  const handleUpdate = () => {
    // 方式1:直接修改(Pinia允许,无需mutation)
    userStore.name = '李四'

    // 方式2:批量修改($patch方法)
    userStore.$patch({
      name: '李四',
      role: 'admin'
    })

    // 方式3:$patch函数(复杂修改)
    userStore.$patch((state) => {
      state.name = '李四'
    })
  }
</script>

(3)Getter:派生状态

与Vuex的Getter类似,Pinia的Getter是基于State的计算属性,支持缓存和依赖追踪。

使用Getter

vue

<template>
  <p>是否为管理员:{{ userStore.isAdmin }}</p>
</template>

<script setup>
  import {useUserStore} from './store/user'

  const userStore = useUserStore()
</script>

(4)Action:处理所有逻辑

Pinia的Action是最核心的部分,既可以处理同步操作,也可以处理异步操作,且可以直接修改State(无需像Vuex那样通过Mutation)。

调用Action

vue

<script setup>
  import {useUserStore} from './store/user'

  const userStore = useUserStore()

  // 调用同步action
  userStore.updateName('李四')

  // 调用异步action
  userStore.fetchUserInfo()
</script>

2. Pinia的Composition API风格

Pinia还支持用Composition API风格定义Store,更贴合Vue3的写法:

javascript
// store/cart.js(Composition API风格)
import {defineStore} from 'pinia'
import {ref, computed} from 'vue'

export const useCartStore = defineStore('cart', () => {
    // 状态(用ref/reactive定义)
    const items = ref([])

    // Getter(用computed定义)
    const itemCount = computed(() => items.value.length)

    // Action(普通函数)
    const addItem = (item) => {
        items.value.push(item)
    }

    const fetchCart = async () => {
        const res = await fetch('/api/cart')
        items.value = await res.json()
    }

    // 暴露状态、getter、action
    return {items, itemCount, addItem, fetchCart}
})

三、Vuex与Pinia的核心区别

特性Vuex(3.x/4.x)Pinia
核心概念State、Mutation、Action、Getter、ModuleState、Action、Getter(无Mutation,Module由Store替代)
状态修改必须通过Mutation(同步)Action中直接修改(同步/异步均可)
模块化需要Module+namespaced配置每个Store就是独立模块,天然隔离
TypeScript支持需手动定义类型,较繁琐天然支持,类型推导自动完成
代码风格选项式API为主支持选项式和Composition API
DevTools集成支持支持,且能追踪Pinia的Action
体积较大(包含冗余API)较小(精简API)
适用场景Vue2项目、复杂团队协作(严格规范)Vue3项目、追求简洁和TS支持的场景

最关键的差异
Pinia移除了Mutation,允许在Action中直接修改状态,简化了工作流。这是因为Vuex设计时担心“异步修改状态难以追踪”,但实际开发中 Mutation的约束反而增加了代码冗余。Pinia通过更完善的DevTools集成,在保留可追踪性的同时去掉了这层约束。

四、状态管理的最佳实践

无论使用Vuex还是Pinia,良好的状态管理实践都能提升代码可维护性:

1. 数据分治:按业务拆分状态

  • 避免“大而全”的状态设计,按业务域拆分Store/Module(如usercartproduct
  • 每个Store只管理与自身业务相关的状态,避免跨Store依赖
  • 示例:电商应用可拆分为user(用户信息)、cart(购物车)、product(商品)、order(订单)4个Store

2. 异步处理:统一封装API调用

  • 将所有API请求封装在Action中,组件不直接调用API,而是通过Action获取数据
  • 处理异步状态(加载中、成功、失败),提升用户体验:
    javascript
    // Pinia示例:处理异步状态
    export const useProductStore = defineStore('product', {
      state: () => ({
        list: [],
        loading: false,
        error: null
      }),
      actions: {
        async fetchProducts() {
          this.loading = true
          this.error = null
          try {
            const res = await fetch('/api/products')
            this.list = await res.json()
          } catch (err) {
            this.error = err.message
          } finally {
            this.loading = false
          }
        }
      }
    })

3. 状态持久化:避免刷新丢失

通过插件将状态保存到localStoragesessionStorage,解决页面刷新后状态丢失问题:

  • Pinia持久化(使用pinia-plugin-persistedstate):

    javascript
    // main.js
    import { createPinia } from 'pinia'
    import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
    
    const pinia = createPinia()
    pinia.use(piniaPluginPersistedstate) // 安装插件
    
    // 定义Store时开启持久化
    export const useUserStore = defineStore('user', {
      state: () => ({ name: '张三' }),
      persist: true // 持久化该Store
    })
  • Vuex持久化(使用vuex-persistedstate):

    javascript
    import createPersistedState from 'vuex-persistedstate'
    
    const store = new Vuex.Store({
      // ...其他配置
      plugins: [createPersistedState()] // 持久化插件
    })

4. 避免过度使用状态管理

  • 并非所有状态都需要放入Store:组件私有状态(如表单临时输入)应放在组件内部
  • 跨组件共享、影响全局UI(如登录状态、主题设置)的状态才需要放入Store
  • 小应用可直接用provide/inject或事件总线,避免引入状态管理库增加复杂度

总结

Vuex和Pinia都是优秀的Vue状态管理方案,选择时可参考:

  • 若项目基于Vue2,或需要严格的状态修改规范,选Vuex;
  • 若项目基于Vue3,或重视开发效率、TypeScript支持,选Pinia(官方推荐)。

核心目标是通过状态管理工具实现“可预测的状态变化”:明确状态的来源、修改方式和依赖关系。无论使用哪种工具,遵循“数据分治、职责单一”的原则,才能让状态管理真正提升开发效率,而非成为负担。