Обычный пользовательский опыт — фильтровать результаты по своему усмотрению — просто вспомните, когда в последний раз вы проголодались и искали рестораны со средним рейтингом › 4 звезды в радиусе пары миль.
Работая над нашим первым проектом для школы Flatiron, моя команда хотела предложить пользователям такую же свободу. Мы взяли данные из API службы национальных парков, думая, что было бы здорово создать одностраничное приложение, в котором можно фильтровать парки по активности или состоянию.
Однако начинающему программисту может быть сложно брать внешние данные и манипулировать ими так, как вам нужно. Гораздо сложнее, чем убедить себя, что да, вы действительно должны запланировать поездку в национальный парк Акадия.
Вот несколько подходов к фильтрации массивов в JavaScript, которые могут помочь в подобной ситуации, в частности, путем нахождения пересечения двух массивов. Вы можете думать о пересечениикак онаборе элементов, присутствующих в обоих массивах.
Методы массива
Давайте начнем с обзора нескольких полезных методов, имеющихся в нашем распоряжении. Мы будем использовать их в наших примерах ниже.
включает в себя( )
- Принимает один параметр, наш «поисковый запрос».
- Возвращает
true
илиfalse
в зависимости от того, существует ли параметр в массиве.
фильтр( )
- Принимает один параметр, функцию обратного вызова. Здесь мы можем создать любую функцию, которая возвращает
true
для элементов, которые мы хотим сохранить в результатах, иfalse
в противном случае.includes()
выше соответствует этим критериям — это пригодится позже. - Возвращает копию массива с результатами
Это быстрые определения. Вы можете найти полную ссылку на каждый метод в MDN здесь и здесь.
Сравнение плоских массивов
Если в наших массивах нет вложенных элементов, filter()
и includes()
могут помочь нам вернуть общие элементы всего несколькими строками кода.
Чтобы продолжить нашу тему парков, скажем, у нас есть два массива — один для доступных летних занятий и один для зимних.
const summer = ['hiking', 'swimming', 'guided tours', 'museum exhibits'] const winter = ['skiing', 'snowboarding', 'guided tours', 'museum exhibits']
Если нас интересуют только круглогодичные действия, нам нужен массив только со строками из зимы и лета. Мы можем использовать filter()
в нашем летнем массиве, а затем создать функцию обратного вызова, которая принимает по одному activity
за раз и проверяет его наличие в нашем зимнем массиве.
const yearRound = summer.filter(activity => winter.includes(activity)) // => [ 'guided tours', 'museum exhibits' ]
Вы можете подумать о подходе здесь как:
summer.filter
Начните с летнего массиваactivity =>
Посмотрите на каждое действие (строка)winter.includes(activity)
Определите, есть ли в зимнем массиве совпадающая активность (строка). Если да, верните полученную копию летнего массива.
Сравнение вложенных массивов
Скорее всего, массивы, которые мы хотели бы сравнить, не будут простыми строками, особенно при работе с внешним API. В этом примере мы рассмотрим, как мы можем проверить, содержит ли вложенный массив какие-либо элементы другого.
Допустим, теперь у нас есть массив объектов для представления наших парков. У каждого парка есть название, штат и доступные занятия. Последний имеет свой собственный массив в качестве значения.
Отдельно у нас есть массив, который мы назовем userInput
— любые действия, которые наши пользователи выбрали как интересные для них и с которыми они хотят взаимодействовать в нашем приложении.
const parks = [ { name: 'Acadia National Park', state: 'Maine', activities: ['boating', 'camping', 'fishing', 'skiing'] }, { name: 'Olympic National Park', state: 'Washington', activities: ['hiking', 'snowshoeing', 'tubing', 'kayaking'] }, { name: 'Arches National Park', state: 'Utah', activities: ['canyoneering', 'climbing', 'stargazing', 'horse trekking'] } ] const userInputs = ['hiking', 'stargazing']
Помните, что includes()
принимает один элемент поиска. Если мы попытаемся сопоставить наше точное решение выше с чем-то вроде parks.filter(park => userInputs.includes(park.activities))
, мы не получим никаких результатов.
Так как же нам получить доступ к этому вложенному массиву? Нам нужно расширить нашу функцию обратного вызова, чтобы перейти от объекта parks
к parks.activities
.
const filteredParks = parks.filter(park => { *// add filter to the parks array and pass each park to the callback function for (const activity of park.activities) { *// go through each activity for this park if (userInputs.includes(activity)){ *// if our user inputs array also has the same activity string return true *// this park meets our condition *// include the park in the array copy returned by .filter() } } }) /* => [ { name: 'Olympic National Park', state: 'Washington', activities: [ 'hiking', 'snowshoeing', 'tubing', 'kayaking' ] }, { name: 'Arches National Park', state: 'Utah', activities: [ 'canyoneering', 'climbing', 'stargazing', 'horse trekking' ] } ] */
Здесь наш цикл for приближает нас к информации, которая нас действительно интересует, к действиям, пока мы не сможем проверить, включает ли интерес пользователя действия, связанные с конкретным парком.
Поскольку функция обратного вызова для filter()
должна возвращать истинное или ложное значение, мы вручную возвращаем true
, если интересы нашего пользователя включают нашу строку активности. В наших результатах мы получаем матч в Олимпийском национальном парке 'hiking'
и матч в Национальном парке Арчес 'stargazing'
.
Если вы не работаете с большим количеством данных, это может сработать. Однако важно отметить, что в настоящее время мы просматриваем каждоевыбранное пользователем действие для каждогоэлемента каждогомассива действий в наших парках. Это станет более неэффективным, если длина наших массивов увеличится.
Один из способов смягчить это — использовать набор. На простейшем уровне наборы представляют собой набор значений. У них есть несколько важных отличий от массивов, и для более подробного ознакомления я бы порекомендовал это видео в разделе Полное руководство по JS-сетам.
Что нам нужно знать сегодня, так это то, что мы можем проверить, существует ли элемент в наборе быстрее, чем в массиве. Независимо от того, насколько длинным станет наш массив для park.activities
, если мы создадим набор из значений, мы сможем найти интересующий нас activity
за то же время.
const filterParks = parks.filter(park => { *// add filter to the parks array and pass each park to the callback function const parkActivities = new Set(park.activities) *// create a set with all the activities for this specific park for (const activity of userInputs) { *// for each activity the user selected if (parkActivities.has(activity)) { *// if the set has that same activity string return true *// this park meets our condition *// include the park in the array copy returned by .filter() } } }) /* => [ { name: 'Olympic National Park', state: 'Washington', activities: [ 'hiking', 'snowshoeing', 'tubing', 'kayaking' ] }, { name: 'Arches National Park', state: 'Utah', activities: [ 'canyoneering', 'climbing', 'stargazing', 'horse trekking' ] } ] */
Здесь мы используем метод has()
для нашего набора, а не includes()
для нашего массива. Метод также принимает параметр элемента поиска для поиска в наборе.
В этом обновленном подходе нам по-прежнему нужно перебирать каждый парк, но не каждый вложенный массив действий. Чтобы узнать больше о пересечениях и множествах, ознакомьтесь с этим примером от Geeks for Geeks.
И вот, мы отфильтровали наш массив на основе содержимого другого. Я надеюсь, что это поможет вам начать работу с фильтрами в ваших собственных приложениях.