如何在 Vuex 中实现一个复杂的数据过滤或排序逻辑,避免 Getters 过于复杂?

Vuex 进阶:告别 Getter 地狱,解锁数据处理的正确姿势

各位老铁,晚上好!我是你们的老朋友,代码界的段子手,今天咱们聊点硬核的:Vuex 里的数据过滤和排序。

话说咱们用 Vuex 管理状态,一开始风平浪静,Getter 写得那叫一个清爽。但随着业务复杂度蹭蹭往上涨,需求像雨后春笋一样冒出来,Getter 渐渐地…变异了!几百行的 Getter 比比皆是,逻辑嵌套得比俄罗斯套娃还深,维护起来简直是噩梦。

今天,咱就来好好盘盘,怎么才能优雅地处理 Vuex 里的复杂数据,避免 Getter 变成“代码屎山”。

Getter 变成“屎山”的常见症状

首先,咱们得先知道,你的 Getter 到底是不是已经病入膏肓了。看看有没有这些症状:

  • 症状一:代码行数爆炸。 一个 Getter 几百行,甚至上千行,一眼望不到头。
  • 症状二:逻辑嵌套过深。 各种 if...elsefor 循环、三元运算符,层层叠叠,让人眼花缭乱。
  • 症状三:复用性极差。 同一个逻辑,在不同的 Getter 里重复出现,改一个地方,要改好几个地方。
  • 症状四:性能瓶颈。 数据量稍微大一点,页面就卡得像幻灯片。
  • 症状五:可读性极差。 变量命名随意,注释缺失,半年后再看,自己都不知道写的是啥玩意儿。

如果你的 Getter 已经出现以上任何一种症状,那你就得小心了,再不采取措施,你的代码迟早要崩盘!

解决方案一:模块化你的 Getter

当 Getter 的逻辑开始膨胀时,最简单的办法就是拆分。把一个大的 Getter 拆分成多个小的、职责单一的 Getter。

示例:

假设我们有一个存储用户信息的 Vuex 模块:

// store/modules/user.js

const state = {
  users: [
    { id: 1, name: '张三', age: 25, city: '北京', isVip: true },
    { id: 2, name: '李四', age: 30, city: '上海', isVip: false },
    { id: 3, name: '王五', age: 20, city: '深圳', isVip: true },
    { id: 4, name: '赵六', age: 35, city: '北京', isVip: false },
  ]
};

const getters = {
  // 最初的 Getter,所有逻辑都堆在一起
  filteredAndSortedUsers: (state) => {
    // 过滤:筛选出年龄大于 25 岁的用户
    let filteredUsers = state.users.filter(user => user.age > 25);

    // 排序:按照年龄降序排列
    filteredUsers.sort((a, b) => b.age - a.age);

    // 返回结果
    return filteredUsers;
  }
};

export default {
  namespaced: true,
  state,
  getters
};

现在,我们把它拆分成多个小的 Getter:

// store/modules/user.js

const state = {
  users: [
    { id: 1, name: '张三', age: 25, city: '北京', isVip: true },
    { id: 2, name: '李四', age: 30, city: '上海', isVip: false },
    { id: 3, name: '王五', age: 20, city: '深圳', isVip: true },
    { id: 4, name: '赵六', age: 35, city: '北京', isVip: false },
  ]
};

const getters = {
  // 获取所有用户
  allUsers: (state) => state.users,

  // 筛选出年龄大于指定值的用户
  usersOlderThan: (state) => (age) => {
    return state.users.filter(user => user.age > age);
  },

  // 按照年龄降序排列
  sortedByAgeDescending: (state) => (users) => {
    const usersToSort = users || state.users; // 允许传入用户数组,或者使用 state 中的 users
    return [...usersToSort].sort((a, b) => b.age - a.age); // 创建副本以避免直接修改 state
  },

  // 最终的 Getter,组合其他 Getter
  filteredAndSortedUsers: (state, getters) => {
    const olderUsers = getters.usersOlderThan(25);
    return getters.sortedByAgeDescending(olderUsers);
  }
};

export default {
  namespaced: true,
  state,
  getters
};

优点:

  • 职责单一: 每个 Getter 只负责一个特定的逻辑,易于理解和维护。
  • 复用性高: 不同的 Getter 可以互相组合,减少代码重复。
  • 可测试性强: 每个 Getter 都可以单独进行单元测试。

缺点:

  • Getter 的数量可能会增加,需要合理命名和组织。
  • 如果组合的 Getter 过多,可能会增加一些性能开销。

表格总结:

特性 拆分前 拆分后
代码行数
逻辑复杂度
复用性
可维护性

解决方案二:使用计算属性 (Computed Properties)

有时候,Getter 的逻辑仅仅是为了在组件中显示数据,并没有其他的用途。这种情况下,我们可以直接使用计算属性来代替 Getter。

示例:

<template>
  <div>
    <ul>
      <li v-for="user in filteredAndSortedUsers" :key="user.id">
        {{ user.name }} ({{ user.age }})
      </li>
    </ul>
  </div>
</template>

<script>
import { mapState } from 'vuex';

export default {
  computed: {
    ...mapState('user', ['users']),
    filteredAndSortedUsers() {
      // 过滤:筛选出年龄大于 25 岁的用户
      let filteredUsers = this.users.filter(user => user.age > 25);

      // 排序:按照年龄降序排列
      filteredUsers.sort((a, b) => b.age - a.age);

      // 返回结果
      return filteredUsers;
    }
  }
};
</script>

优点:

  • 代码更简洁: 直接在组件中处理数据,减少了 Vuex 的代码量。
  • 更灵活: 可以根据组件的需求,灵活地调整过滤和排序逻辑。

缺点:

  • 复用性降低: 如果多个组件都需要相同的过滤和排序逻辑,就需要重复编写代码。
  • 可测试性降低: 计算属性的逻辑不容易进行单元测试。

使用场景:

  • 当过滤和排序逻辑只在一个组件中使用时。
  • 当过滤和排序逻辑比较简单时。

解决方案三:使用 Lodash 或其他工具库

Lodash 提供了丰富的函数,可以方便地进行数据过滤和排序。使用 Lodash 可以大大简化 Getter 的代码。

示例:

import _ from 'lodash';

// store/modules/user.js

const state = {
  users: [
    { id: 1, name: '张三', age: 25, city: '北京', isVip: true },
    { id: 2, name: '李四', age: 30, city: '上海', isVip: false },
    { id: 3, name: '王五', age: 20, city: '深圳', isVip: true },
    { id: 4, name: '赵六', age: 35, city: '北京', isVip: false },
  ]
};

const getters = {
  filteredAndSortedUsers: (state) => {
    return _(state.users)
      .filter(user => user.age > 25)
      .orderBy(['age'], ['desc']) // 使用 Lodash 的 orderBy 函数进行排序
      .value();
  }
};

export default {
  namespaced: true,
  state,
  getters
};

优点:

  • 代码更简洁: Lodash 提供了各种便捷的函数,可以大大简化代码。
  • 性能更好: Lodash 的函数经过了优化,通常比手写的代码性能更好。
  • 可读性更强: Lodash 的函数命名清晰,易于理解。

缺点:

  • 需要引入 Lodash 库,增加了项目的体积。
  • 需要学习 Lodash 的 API。

表格总结:

特性 手写代码 Lodash
代码行数
性能 可能较差 较好
可读性 可能较差 较好

解决方案四:使用 Mutations 和 Actions 进行预处理

如果过滤和排序的逻辑比较复杂,而且需要在多个地方使用,可以考虑使用 Mutations 和 Actions 进行预处理。

示例:

// store/modules/user.js

const state = {
  users: [], // 初始为空
  filteredAndSortedUsers: [] // 用于存储过滤和排序后的数据
};

const mutations = {
  // 设置过滤和排序后的用户数据
  setFilteredAndSortedUsers(state, users) {
    state.filteredAndSortedUsers = users;
  }
};

const actions = {
  // 过滤和排序用户数据
  filterAndSortUsers({ commit, state }, filterOptions) {
    // 从 API 获取用户数据 (这里使用模拟数据)
    const usersFromApi = [
        { id: 1, name: '张三', age: 25, city: '北京', isVip: true },
        { id: 2, name: '李四', age: 30, city: '上海', isVip: false },
        { id: 3, name: '王五', age: 20, city: '深圳', isVip: true },
        { id: 4, name: '赵六', age: 35, city: '北京', isVip: false },
    ];

    // 过滤:根据 filterOptions 进行过滤
    let filteredUsers = usersFromApi.filter(user => {
      if (filterOptions.ageGreaterThan && user.age <= filterOptions.ageGreaterThan) {
        return false;
      }
      // 可以添加更多过滤条件
      return true;
    });

    // 排序:根据 filterOptions 进行排序
    filteredUsers.sort((a, b) => {
      if (filterOptions.sortBy === 'age') {
        return filterOptions.sortOrder === 'desc' ? b.age - a.age : a.age - b.age;
      }
      // 可以添加更多排序规则
      return 0;
    });

    // 提交 Mutation,更新 state
    commit('setFilteredAndSortedUsers', filteredUsers);
  }
};

const getters = {
  // 获取过滤和排序后的用户数据
  filteredAndSortedUsers: (state) => state.filteredAndSortedUsers
};

export default {
  namespaced: true,
  state,
  mutations,
  actions,
  getters
};

使用:

<template>
  <div>
    <ul>
      <li v-for="user in filteredAndSortedUsers" :key="user.id">
        {{ user.name }} ({{ user.age }})
      </li>
    </ul>
    <button @click="applyFilters">Apply Filters</button>
  </div>
</template>

<script>
import { mapState, mapActions } from 'vuex';

export default {
  computed: {
    ...mapState('user', ['filteredAndSortedUsers'])
  },
  methods: {
    ...mapActions('user', ['filterAndSortUsers']),
    applyFilters() {
      // 调用 Action,进行过滤和排序
      this.filterAndSortUsers({
        ageGreaterThan: 25,
        sortBy: 'age',
        sortOrder: 'desc'
      });
    }
  }
};
</script>

优点:

  • 复用性高: 可以在多个组件中调用同一个 Action,进行过滤和排序。
  • 逻辑清晰: 将过滤和排序的逻辑放在 Actions 中,使 Getter 更加简洁。
  • 异步处理: 可以在 Actions 中进行异步操作,例如从 API 获取数据。

缺点:

  • 需要维护额外的 State 变量,用于存储过滤和排序后的数据。
  • 如果过滤和排序的逻辑比较简单,使用 Actions 可能会显得过于复杂。

表格总结:

特性 Getter 直接处理 Actions 预处理
复用性
逻辑清晰度
异步处理 不支持 支持

最佳实践总结

  • 保持 Getter 的简单性: 尽量让 Getter 只做简单的状态读取和转换,避免复杂的逻辑。
  • 模块化你的 Getter: 将大的 Getter 拆分成多个小的、职责单一的 Getter。
  • 使用计算属性: 当过滤和排序逻辑只在一个组件中使用时,可以使用计算属性代替 Getter。
  • 使用 Lodash 或其他工具库: Lodash 提供了丰富的函数,可以方便地进行数据过滤和排序。
  • 使用 Mutations 和 Actions 进行预处理: 如果过滤和排序的逻辑比较复杂,而且需要在多个地方使用,可以考虑使用 Mutations 和 Actions 进行预处理。
  • 合理命名: 给你的 Getter、Mutations 和 Actions 命名时,要清晰明了,让人一眼就能看出它们的作用。
  • 编写注释: 给你的代码添加必要的注释,方便自己和他人理解。

选择哪种方案取决于你的具体需求和项目的复杂度。没有银弹,只有最适合你的方案。

希望今天的分享对你有所帮助。记住,写代码就像做菜,食材再好,也需要好的烹饪技巧才能做出美味佳肴。好的架构和代码组织才能让你的 Vuex 应用更加健壮和易于维护。下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注