如何在许多条件下整理JavaScript过滤器功能?

时间:2017-11-10 09:08:52

标签: javascript

我正在使用React,但我认为这只是一个普通的JavaScript问题。在我的数据模型中,我有一个对象数组。这是一个缩短版本:

this.props.exercises = [
  {
    name: 'Push up',
    equipment: 'none',
    group: 'push'
  },
  {
    name: 'Bench press',
    equipment: 'Barbell',
    group: 'push'
  },
  {
    name: 'Pull-Up Bar',
    equipment: 'Pull-Up Bar',
    group: 'pull'
  },
  {
    name: 'Dumbbell / Kettlebell Squat',
    equipment: ['Dumbbell', 'Kettlebell'],
    group: 'legs'
  }
]

我需要对这个数组进行一些非常复杂的过滤。如果项目的名称与selectedExercise字符串匹配,则始终需要返回该字符串。如果这个条件不匹配,那么我需要以下条件:

如果用户选择了一个组,则只返回该组中的练习。

如果用户输入了搜索文本,则结果也应按此过滤。

用户指定他们拥有的设备,然后也应该过滤结果。练习不需要任何设备,1件设备,或者有一系列可能的设备选择。

我的代码可以使用,但我觉得它非常脆弱,如果出现问题,将会成为调试的噩梦。什么是更好的方法?功能性编程可以解决吗?

renderExercises() {
    const {selectedType} = this.state;
    const allExercises = this.props.exercises;

    // If there is search text then create a var for it
    let searchText = false;
    if (this.searchText && this.searchText.value.length > 0) {
      searchText = this.searchText.value.toLowerCase();
    }

    return (
      allExercises
      // Set the active exercise to have an active property
        .map((item) => {
          if (this.props.chosenExercise === item.name) {
            item.active = true;
          } else {
            item.active = false;
          }
          return item;
        })
        .filter((item) => {
          // If the exercise is active then return true
          if (item.active === true) {
            return true
          }
          // Filter by the exercise group if one is selected
          if (item.group !== selectedType && selectedType !== 'all') {
            return false;
          }
          // If there is search text then filter out non matches
          if (searchText && !item.name.toLowerCase().includes(searchText)) {
            return false;
          }

          // EQUIPMENT CONDITIONS
          // If the exercise doesn't need any equipment then return true
          if (item.equipment === 'none') {
            return true;
          }

          // If the user has all equipment then return true
          if (this.props.equipmentSelectionState === 'all') {
            return true;
          }

          // If the item only has 1 piece of equipment then the type will be a string not an array
          if (typeof item.equipment == 'string' || item.equipment instanceof String) {
            // If the selected equipment array contains the items's required equipment then return true
            if (this.props.equipmentSelection.includes(item.equipment)) {
              return true;
            } else {
              return false;
            }
          } else {
            let test = false;
            item.equipment.forEach((itemEquipment => {
              if (this.props.equipmentSelection.includes(itemEquipment)) {
                test = true;
              }
            }));
            if (test === true) {
              return true;
            }
          }

          return false;
        })
        // Sort by name
        .sort((a, b) => {
          if (a.name < b.name)
            return -1;
          if (a.name > b.name)
            return 1;
          return 0;
        })
        .map((item) => {
          return (
            <ChooseAnExercises
              key={item.name}
              name={item.name}
              active={item.active}
              setNumber={this.props.number}
              updateValue={this.props.updateValue}
            />
          )
        })
    )
  }

4 个答案:

答案 0 :(得分:0)

您可以仅使用true条件缩小过滤器部分,并首先采用一般条件对其进行排序,然后进行更多类型的昂贵操作,例如迭代数组。

.filter(item =>
    item.active ||
    item.equipment === 'none' ||
    this.props.equipmentSelectionState === 'all' ||
    selectedType === 'all' ||
    item.group === selectedType ||
    searchText && item.name.toLowerCase().includes(searchText) ||
    (Array.isArray(item.equipment)
        ? item.equipment
        : [item.equipment]
    ).some(itemEquipment => this.props.equipmentSelection.includes(itemEquipment))
)

所有条件都使用逻辑OR链接。如有必要,将字符串转换为数组,以便使用相同的检查进行迭代。

答案 1 :(得分:0)

您必须多次过滤。试试这个简单的方法。

演示

&#13;
&#13;
var exercises = [{
    name: 'Push up',
    equipment: 'none',
    group: 'push'
  },
  {
    name: 'Bench press',
    equipment: 'Barbell',
    group: 'push'
  },
  {
    name: 'Pull-Up Bar',
    equipment: 'Pull-Up Bar',
    group: 'pull'
  },
  {
    name: 'Dumbbell / Kettlebell Squat',
    equipment: ['Dumbbell', 'Kettlebell'],
    group: 'legs'
  }
];
var searchName = "Push up";
var searchGroup = "push";
var searchText = "Dumbbell";

//as per priority do the name search first.
var directSeachNameMatches = exercises.filter((s) => s.name == searchName);
//Do a group search if direct name search doesn't give anything back
var groupMatches = directSeachNameMatches.length ? directSeachNameMatches : exercises.filter((s) => s.group == searchGroup);
//Do a text search if group name search doesn't give anything back
var searchTextMatches = groupMatches.length ? groupMatches : exercises.filter(function(s) {
  if (!Array.isArray(s.equipment)) {
    s.equipment = [s.equipment]
  };
  return s.equipment.indexOf(searchText) != -1;
});
var finalMatch = searchTextMatches ;

console.log(finalMatch);
&#13;
&#13;
&#13;

答案 2 :(得分:0)

从一些基本的简化开始:

  • if (x === true)应为if (x)(除非您的变量可以保存不同类型的值,但实际上它不应该首先出现)
  • if (x) return true; else return false应为return x
  • test = false; x.forEach(v => { if (p(v)) test = true; })应为test = x.some(p)

然后,更改您的数据。如前所述,您不应在同一位置使用不同类型的值,特别是equipment不是stringString实例或{{1} }}。只是让它始终是一个字符串数组!

  • 而不是值Array,有一个空数组
  • 而不是字符串,有一个单独的数组,该字符串作为其元素

现在我们至少可以简化到

'none'

现在将其转换为单个布尔表达式:

return (
  allExercises
  // Set the active exercise to have an active property
    .map((item) => {
      item.active = (this.props.chosenExercise === item.name);
      return item;
    })
    .filter((item) => {
      // If the exercise is active then return true
      if (item.active) {
        return true
      }
      // Filter by the exercise group if one is selected
      if (item.group !== selectedType && selectedType !== 'all') {
        return false;
      }
      // If there is search text then filter out non matches
      if (searchText && !item.name.toLowerCase().includes(searchText)) {
        return false;
      }

      // If the exercise doesn't need any equipment then return true
      if (!item.equipment.length) {
        return true;
      }
      // If the user has all equipment then return true
      if (this.props.equipmentSelectionState === 'all') {
        return true;
      }
      return item.equipment.some(itemEquipment =>
        this.props.equipmentSelection.includes(itemEquipment)
      );
    })

答案 3 :(得分:0)

我意识到尝试不过滤活动项目会阻止我链接过滤器,我认为这是最简单,更易读的解决方案。因此,我删除活动项并将其存储在单独的对象中,并在我完成过滤后将其添加回来。

return (
      allExercises
      // Set the active exercise to have an active property
        .filter((item) => {
          if (this.props.chosenExercise === item.name) {
            item.active = true;
            Object.assign(activeItem, item);
            return false; // Remove the active element so we can add it later independent of filtering
          } else {
            item.active = false;
            return true;
          }
        })
        .filter(item => {
          const {selectedType} = this.state;
          if (selectedType === 'all') return true; // If 'all' exercise group is selected then let everything pass
          if (item.group === selectedType) return true; // If the selected exercise group matches the exercise's group then pass
        })
        .filter(item => {
          if (!searchText) {
            return true; // If no search text let everything pass
          } else if (item.name.toLowerCase().includes(searchText)) {
            return true; // If there is search text then match against this
          }
        })
        .filter(item => {
          if (item.equipment === 'none') return true; // If exercise doesn't need equipment then always pass
          if (this.props.equipmentSelectionState === 'all') return true; // If user has all equipment selected then always pass
          // item.equipment is a string if there is only 1 piece of equipment required by the exercise
          if (typeof item.equipment === 'string') {
            // Check the users selected equipment for a match
            if (this.props.equipmentSelection.includes(item.equipment)) {
              return true;
            }
          } else {
            // item.equipment is an array
            let test = false;
            this.props.equipmentSelection.forEach((equipmentSelection) => {
              item.equipment.forEach(itemEquipment => {
                if (itemEquipment === equipmentSelection) {
                  test = true;
                }
              });
            });
            return test;
          }
        })
        // Sort by name
        .concat(activeItem) // Add the active item we removed earlier
        .filter((item) => {
          // Filter out the empty result which is pushed when no exercise is active
          if (Object.keys(item).length > 0) return true;
        })
        .sort((a, b) => {
          // Sort alphabetically
          if (a.name < b.name)
            return -1;
          if (a.name > b.name)
            return 1;
          return 0;
        })
        .map((item) => {
          return (
            <ChooseAnExercises
              key={item.name}
              name={item.name}
              active={item.active}
              setNumber={this.props.number}
              updateValue={this.props.updateValue}
            />
          )
        })
    )