Vuex 4 for Vue 3

什么是 Vuex?

Vuex 是 Vue.js 的官方状态管理库,专为 Vue.js 应用程序设计。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

为什么需要 Vuex?

在复杂的 Vue 应用中,组件间的状态共享和通信会变得困难:

  • 多层嵌套组件间的状态传递繁琐
  • 兄弟组件间的通信复杂
  • 多个视图依赖同一状态
  • 来自不同视图的行为需要变更同一状态

Vuex 解决了这些问题,提供了:

  • 单一数据源 - 所有组件共享同一状态源
  • 可预测的状态变更 - 通过严格模式确保状态变更符合规则
  • 模块化组织 - 大型应用状态管理的解决方案

核心概念

1. State - 状态存储

State 是应用状态的单一数据源,存储在 Vuex store 中。

javascript 复制代码
import { createStore } from 'vuex';

const store = createStore({
  state: {
    count: 0,
    user: null,
    todos: []
  }
});

2. Getters - 状态派生

Getters 用于从 state 中派生出新状态,类似于计算属性。

javascript 复制代码
const store = createStore({
  state: {
    todos: [
      { id: 1, text: 'Learn Vue', done: true },
      { id: 2, text: 'Master Vuex', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done);
    },
    getTodoById: state => id => {
      return state.todos.find(todo => todo.id === id);
    }
  }
});

3. Mutations - 状态变更

Mutations 是更改 Vuex store 中状态的唯一方法,必须是同步函数。

javascript 复制代码
const store = createStore({
  state: {
    count: 1
  },
  mutations: {
    increment(state) {
      state.count++;
    },
    incrementBy(state, payload) {
      state.count += payload.amount;
    }
  }
});

4. Actions - 异步操作

Actions 用于提交 mutations,可以包含任意异步操作。

javascript 复制代码
const store = createStore({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++;
    }
  },
  actions: {
    incrementAsync({ commit }) {
      setTimeout(() => {
        commit('increment');
      }, 1000);
    },
    fetchUserData({ commit }, userId) {
      return axios.get(`/api/users/${userId}`)
        .then(response => {
          commit('setUser', response.data);
        });
    }
  }
});

5. Modules - 模块化

大型应用可以将 store 分割成模块,每个模块拥有自己的 state、mutations、actions、getters。

javascript 复制代码
const userModule = {
  namespaced: true,
  state: () => ({
    profile: null
  }),
  mutations: {
    setProfile(state, profile) {
      state.profile = profile;
    }
  },
  actions: {
    fetchProfile({ commit }, userId) {
      // 获取用户资料
    }
  }
};

const store = createStore({
  modules: {
    user: userModule,
    products: productsModule
  }
});

Vuex 4 在 Vue 3 中的使用

安装与配置

bash 复制代码
npm install vuex@next --save
javascript 复制代码
// main.js
import { createApp } from 'vue';
import { createStore } from 'vuex';
import App from './App.vue';

// 创建 Vuex store
const store = createStore({
  state() {
    return {
      count: 0
    };
  },
  mutations: {
    increment(state) {
      state.count++;
    }
  }
});

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

在组合式 API 中使用 Vuex

vue 复制代码
<script>
import { computed } from 'vue';
import { useStore } from 'vuex';

export default {
  setup() {
    const store = useStore();
    
    const count = computed(() => store.state.count);
    
    const increment = () => {
      store.commit('increment');
    };
    
    const incrementAsync = () => {
      store.dispatch('incrementAsync');
    };
    
    return {
      count,
      increment,
      incrementAsync
    };
  }
};
</script>

在选项式 API 中使用 Vuex

vue 复制代码
<script>
export default {
  computed: {
    count() {
      return this.$store.state.count;
    },
    // 使用 mapState 辅助函数
    ...mapState(['count']),
    ...mapState({
      countAlias: 'count'
    })
  },
  methods: {
    increment() {
      this.$store.commit('increment');
    },
    // 使用 mapMutations 辅助函数
    ...mapMutations(['increment']),
    ...mapMutations({
      add: 'increment'
    }),
    
    // Actions
    ...mapActions(['incrementAsync']),
    ...mapActions({
      addAsync: 'incrementAsync'
    })
  }
};
</script>

高级用法与最佳实践

1. 严格模式

开启严格模式后,任何非 mutation 函数的状态变更都会抛出错误。

javascript 复制代码
const store = createStore({
  strict: process.env.NODE_ENV !== 'production',
  state: { ... },
  mutations: { ... }
});

2. 表单处理

使用双向绑定处理表单时,需要使用计算属性的 setter:

vue 复制代码
<template>
  <input v-model="message">
</template>

<script>
import { computed } from 'vue';
import { useStore } from 'vuex';

export default {
  setup() {
    const store = useStore();
    
    const message = computed({
      get: () => store.state.message,
      set: value => store.commit('updateMessage', value)
    });
    
    return { message };
  }
};
</script>

3. 模块动态注册

在 store 创建之后,可以使用 store.registerModule 方法动态注册模块:

javascript 复制代码
store.registerModule('myModule', {
  state: () => ({ ... }),
  mutations: { ... }
});

4. 插件开发

Vuex 插件是一个函数,它接收 store 作为唯一参数:

javascript 复制代码
const myPlugin = store => {
  // 当 store 初始化后调用
  store.subscribe((mutation, state) => {
    // 每次 mutation 之后调用
    console.log(mutation.type);
    console.log(mutation.payload);
  });
};

const store = createStore({
  plugins: [myPlugin],
  // ...
});

5. 状态持久化

使用插件实现 Vuex 状态持久化:

javascript 复制代码
import createPersistedState from 'vuex-persistedstate';

const store = createStore({
  plugins: [createPersistedState()],
  state: {
    user: null,
    preferences: {}
  }
});

实战案例:电商应用状态管理

1. 项目结构

bash 复制代码
src/
├── store/
│   ├── index.js          # 组装模块并导出 store
│   ├── modules/
│   │   ├── cart.js       # 购物车模块
│   │   ├── products.js   # 商品模块
│   │   ├── user.js       # 用户模块
│   │   └── orders.js     # 订单模块
│   └── plugins/
│       └── persistence.js # 持久化插件

2. 购物车模块实现

javascript 复制代码
// store/modules/cart.js
export default {
  namespaced: true,
  state: () => ({
    items: [],
    total: 0
  }),
  mutations: {
    ADD_TO_CART(state, product) {
      const existingItem = state.items.find(item => item.id === product.id);
      
      if (existingItem) {
        existingItem.quantity++;
      } else {
        state.items.push({ ...product, quantity: 1 });
      }
      
      state.total += product.price;
    },
    REMOVE_FROM_CART(state, productId) {
      const index = state.items.findIndex(item => item.id === productId);
      
      if (index !== -1) {
        const [removedItem] = state.items.splice(index, 1);
        state.total -= removedItem.price * removedItem.quantity;
      }
    },
    CLEAR_CART(state) {
      state.items = [];
      state.total = 0;
    }
  },
  actions: {
    async checkout({ commit, state }) {
      try {
        const order = {
          items: state.items,
          total: state.total,
          timestamp: new Date().toISOString()
        };
        
        // 发送订单到服务器
        await axios.post('/api/orders', order);
        
        // 清空购物车
        commit('CLEAR_CART');
        
        return order;
      } catch (error) {
        throw new Error('Checkout failed');
      }
    }
  },
  getters: {
    cartItemCount: state => state.items.length,
    isProductInCart: state => productId => {
      return state.items.some(item => item.id === productId);
    }
  }
};

3. 在组件中使用

vue 复制代码
<script>
import { computed } from 'vue';
import { useStore } from 'vuex';

export default {
  setup() {
    const store = useStore();
    
    // 访问购物车状态
    const cartItems = computed(() => store.state.cart.items);
    const cartTotal = computed(() => store.state.cart.total);
    
    // 访问购物车 getters
    const itemCount = computed(() => store.getters['cart/cartItemCount']);
    
    // 操作购物车
    const addToCart = product => {
      store.commit('cart/ADD_TO_CART', product);
    };
    
    const removeFromCart = productId => {
      store.commit('cart/REMOVE_FROM_CART', productId);
    };
    
    const checkout = async () => {
      try {
        await store.dispatch('cart/checkout');
        alert('Order placed successfully!');
      } catch (error) {
        alert(error.message);
      }
    };
    
    return {
      cartItems,
      cartTotal,
      itemCount,
      addToCart,
      removeFromCart,
      checkout
    };
  }
};
</script>

4. 模块间通信

javascript 复制代码
// store/modules/products.js
export default {
  namespaced: true,
  state: () => ({
    products: []
  }),
  actions: {
    async loadProducts({ commit }) {
      const products = await axios.get('/api/products');
      commit('SET_PRODUCTS', products);
    },
    
    // 模块间通信示例
    async addToCartFromProduct({ dispatch }, product) {
      // 调用购物车模块的 action
      await dispatch('cart/addToCart', product, { root: true });
      
      // 显示通知
      dispatch('notifications/show', {
        message: `${product.name} added to cart!`,
        type: 'success'
      }, { root: true });
    }
  }
};

Vuex 与 Pinia 的比较

特性 Vuex Pinia
Vue 3 支持 Vuex 4 支持 专为 Vue 3 设计
类型支持 有限支持 一流的类型支持
开发体验 较复杂 更简洁直观
模块系统 需要命名空间 自动命名空间
组合式 API 支持 原生支持
插件系统 完善 兼容 Vuex 插件
大小 ~4KB ~1KB
学习曲线 较陡峭 较平缓
官方维护 是(Vue 核心团队)

最佳实践总结

  1. 模块化组织:按功能划分模块,保持单一职责原则
  2. 严格命名规范:使用一致的命名规则(如 actions 使用驼峰式,mutations 使用大写)
  3. 异步操作处理:所有异步操作放在 actions 中,mutations 只处理同步状态变更
  4. 避免直接修改 state:始终通过 mutations 修改状态
  5. 合理使用 getters:对于派生状态使用 getters,避免重复计算
  6. 模块间通信:优先使用 actions 进行模块间通信
  7. 类型安全:在 TypeScript 项目中利用 Vuex 的类型支持
  8. 性能优化:避免在 getters 中进行复杂计算,必要时使用缓存
  9. 状态持久化:对需要持久化的状态使用插件
  10. 测试策略:单元测试 mutations 和 getters,集成测试 actions

迁移到 Pinia

随着 Pinia 成为 Vue 官方推荐的状态管理库,新项目建议使用 Pinia:

javascript 复制代码
// 使用 Pinia 的示例
import { defineStore } from 'pinia';

export const useCartStore = defineStore('cart', {
  state: () => ({
    items: [],
    total: 0
  }),
  actions: {
    addToCart(product) {
      // 直接修改 state
      const existingItem = this.items.find(item => item.id === product.id);
      
      if (existingItem) {
        existingItem.quantity++;
      } else {
        this.items.push({ ...product, quantity: 1 });
      }
      
      this.total += product.price;
    }
  },
  getters: {
    itemCount: (state) => state.items.length
  }
});

结论

Vuex 4 为 Vue 3 提供了强大的状态管理解决方案,特别适合大型复杂应用。它通过集中式的状态管理和严格的变更规则,使应用状态变得可预测和易于调试。

对于新项目,建议考虑使用 Pinia,它提供了更简洁的 API 和更好的开发体验,同时保留了 Vuex 的核心概念。对于现有使用 Vuex 的项目,Vuex 4 仍然是一个稳定可靠的选择。

无论选择哪种状态管理方案,核心原则是一致的:

  1. 集中管理共享状态
  2. 规范状态变更流程
  3. 模块化组织代码
  4. 保持状态的可预测性和可维护性
相关推荐
我想说一句8 分钟前
JavaScript数组:轻松愉快地玩透它
前端·javascript
binggg10 分钟前
AI 编程不靠运气,Kiro Spec 工作流复刻全攻略
前端·claude·cursor
ye空也晴朗19 分钟前
基于eggjs+mongodb实现后端服务
前端
慕尘_22 分钟前
对于未来技术的猜想:Manus as a Service
前端·后端
爱学习的茄子27 分钟前
JS数组高级指北:从V8底层到API骚操作,一次性讲透!
前端·javascript·深度学习
小玉子29 分钟前
webpack未转译第三方依赖axios为es5导致低端机型功能异常
前端
爱编程的喵31 分钟前
React状态管理:从useState到useReducer的完美进阶
前端·react.js
markyankee10133 分钟前
Vue-Router:构建现代化单页面应用的路由引擎
前端·vue.js
Java水解34 分钟前
Spring WebFlux 与 WebClient 使用指南
前端
冉冉同学37 分钟前
【HarmonyOS NEXT】解决Repeat复用导致Image加载图片展示的是上一张图片的问题
android·前端·客户端