Как я могу создать в JavaScript цепочку фильтров в стиле lodash wrap?

Чтобы было ясно, учитывая массив объектов, вероятно, с однородными ключами:

const data = [
  { name: "tomato", isHealthy: true, "calories": 300 },
  { name: "french fries", isHealthy: false, "calories": 1000 },
  { name: "lettuce", isHealthy: true, "calories": 100 },
  { name: "nacho cheese", isHealthy: false, "calories": 1200 },
  { name: "boring chicken", isHealthy: true, "calories": 500 },
];

и несколько фильтров говорят:

const isHealthy = function(items) {
  return items.filter(i => i.isHealthy);
};

const caloriesAbove = function(items, calories) {
  return items.filter(i => i.calories > calories);
};

Я хотел бы иметь возможность вызывать цепочку фильтров, как:

wrapper(data).isHealthy().caloriesAbove(500).value.map(...)

Немного трудно понять, как lodash достигает этого. Кроме того, есть ли способ сделать это без явной развертки, чтобы получить значение? Это не является обязательным требованием, так как я думаю, что для этого могут потребоваться нежелательные хаки.

2 ответа

Решение

Вот как это делает lodash:

 function wrapper(items) {
    return {
        value: items,
        isHealthy() { 
          return wrapper(items.filter(it => it.isHealthy));
        },
        caloriesAbove(calories) {
          return wrapper(items.filter(it => it.calories > calories));
        },
    };
 }

Кроме того, есть ли способ сделать это без явной развертки, чтобы получить значение?

Начиная с ES6 вы можете расширять массивы:

  class Items extends Array {
    isHealthy() {
      return this.filter(it => it.isHealthy);
    }
  }

Чтобы превратить существующий массив в элементы, просто выполните Items.from(array) или же new Items(...array) тогда ты можешь:

  items
    .filter(it => it.calories > 2) // native methods can be used, they return Item instances, not Arrays
    .isHealthy() // our custom methods also work
    .map(it => it.name)[0] //its a regular array with indices.

Вы можете комбинировать, например, фильтр + уменьшить без переноса. Array.prototype.filter возвращает новый массив, вы можете применить Array.prototype.reduce прямо на него.

  const sumOfCalories = data
    .filter((el) => el.isHealthy)
    .reduce((totalCal, nextEl) => totalCal + nextEl.calories, 0);

Я прочитал ответ, и мне понравилось, как это сделал @Jonas. Не знал, что вы можете просто расширить массив таким образом. Отсроченный получил мой upvote!

С учетом вышесказанного меня интересует, насколько полезнее такой подход, когда вы можете просто создать функцию фильтра для обработки нескольких значений и передать ее в фильтр массива:

const data = [
  { name: "tomato", isHealthy: true, "calories": 300 },
  { name: "french fries", isHealthy: false, "calories": 1000 },
  { name: "lettuce", isHealthy: true, "calories": 100 },
  { name: "nacho cheese", isHealthy: false, "calories": 1200 },
  { name: "boring chicken", isHealthy: true, "calories": 500 },
];

const myFilterFn = ({isHealthy, calories}) => isHealthy && calories > 200

data.filter(myFilterFn).map(x => console.log(x))

Я вижу значение этого в более общем виде, но для фильтрации это кажется немного излишним.

Просто мои 2 цента.

Другие вопросы по тегам