Как я могу создать в 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 цента.