Обычный пользовательский опыт — фильтровать результаты по своему усмотрению — просто вспомните, когда в последний раз вы проголодались и искали рестораны со средним рейтингом › 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.

И вот, мы отфильтровали наш массив на основе содержимого другого. Я надеюсь, что это поможет вам начать работу с фильтрами в ваших собственных приложениях.