Вычисляемый список и поиск сотрудников
Оглавление серии «Vue 3: кабинет сотрудников «Команда»»
В кабинете появилась реактивная команда, но просмотр полного списка неудобен при росте числа сотрудников. Добавим текстовый поиск и переключатель «Только доступные». Результат этого урока — производная коллекция, которая всегда вычисляется из полного списка и условий отбора. Поиск не удаляет сотрудников, а скрывает неподходящие строки. Именно это различие позволяет свободно сбрасывать фильтр и менять доступность участника.
Рассмотрим знакомые данные. Запрос «ред» должен оставить Ирину по её роли «Редактор», хотя эта последовательность букв отсутствует в имени. При пустом запросе и выключенном переключателе снова видны все три человека. Если сделать Павла доступным, условие «Только доступные» должно начать включать его без отдельного исправления какого-то второго массива.
Источник и вычисляемое значение
Полный список members остаётся источником истины в памяти, query и onlyAvailable хранят намерение пользователя, visibleMembers является computed. Вычисляемое свойство читает зависимости и возвращает подходящие записи. Документация computed объясняет отслеживание реактивных зависимостей и кеширование результата. Наш предметный выбор — какие именно зависимости прочитать и как согласовать два условия поиска.
Полная замена src/App.vue вводит фильтры и счётчик показанных строк. Код не меняет исходные объекты во время вычисления.
<script setup>
import { computed, ref } from 'vue'
import { seedMembers } from './data/members.js'
const members = ref(seedMembers.map(member => ({ ...member })))
const query = ref('')
const onlyAvailable = ref(false)
const visibleMembers = computed(() => {
const term = query.value.trim().toLocaleLowerCase('ru')
return members.value.filter(member => {
const text = `${member.name} ${member.role}`.toLocaleLowerCase('ru')
return text.includes(term) && (!onlyAvailable.value || member.available)
})
})
function toggle(id) {
const member = members.value.find(item => item.id === id)
if (member) member.available = !member.available
}
</script>
<template>
<main>
<h1>Команда</h1>
<label>Имя или роль <input v-model="query" type="search"></label>
<label><input v-model="onlyAvailable" type="checkbox"> Только доступные</label>
<p>Показано {{ visibleMembers.length }} из {{ members.length }}</p>
<p v-if="visibleMembers.length === 0">Подходящих участников нет.</p>
<ul>
<li v-for="member in visibleMembers" :key="member.id">
{{ member.name }} — {{ member.role }}
<span>{{ member.available ? 'Доступен' : 'Занят' }}</span>
<button type="button" @click="toggle(member.id)">Доступность {{ member.name }}</button>
</li>
</ul>
</main>
</template>
Ввод через v-model меняет query, а computed получает новое условие. Слова приводятся к нижнему регистру с русской локалью, пробелы по краям убираются. Поэтому « ИР » и «ир» дают одинаковый поиск по нашему набору. Это ограниченная нормализация: она не исправляет опечатки и не делает «ё» эквивалентным «е». Не обещайте пользователю нечёткий поиск, если код выполняет обычное включение подстроки.
Почему не нужен второй список
Представим неудачный вариант: обработчик ввода один раз записал результат filter в ref filtered. После этого кнопка изменила available у Павла, но обработчик ввода не выполнялся. Зависимость между новым состоянием и видимой коллекцией могла потеряться. Чтобы поддерживать два массива согласованными, пришлось бы помнить об обновлении filtered при каждом событии. Computed выражает зависимость непосредственно и уменьшает число мест, требующих ручной синхронизации.
filter создаёт новый массив, но его элементы остаются теми же объектами участников. Поэтому toggle находит и меняет запись в members, а видимая строка читает связанное значение. Это полезно, пока строка только отображает сотрудника. Для редактирования нескольких полей понадобится отдельный черновик, иначе ввод в отфильтрованную карточку будет сразу изменять общую запись. Такой черновик введём в уроке формы, не смешивая две разные задачи.
Вычисление должно оставаться без побочных эффектов. Не добавляйте в него запись в storage, сетевой запрос или изменение members. Vue может обратиться к computed тогда, когда интерфейсу нужен результат, и такой момент не равен пользовательскому подтверждению действия. Поиск в этом уроке является чистым преобразованием данных. Наблюдатель для сохранения настройки появится отдельно, когда будет определён конкретный внешний эффект и правило восстановления.
Для только доступных участников выражение !onlyAvailable.value || member.available означает: выключенный фильтр пропускает любого, включённый требует true. Затем && соединяет это условие с текстовым поиском. Таким образом сотрудник должен удовлетворить обоим активным критериям. Если вместо && поставить ||, список начнёт пропускать доступных людей независимо от текста. Это типичная предметная ошибка: синтаксис правильный, но смысл кабинета изменился.
Пустой результат и размеры коллекции
Пустой массив после поиска не означает, что команда не существует. Надпись «Показано 0 из 3» объясняет различие между всей коллекцией и её представлением. Когда появится серверная загрузка, ещё придётся отличать отсутствие данных от незавершённого запроса и его ошибки. Сейчас данные уже находятся в памяти, поэтому отдельного состояния загрузки нет. Преждевременный индикатор «загружаем» на каждом вводе говорил бы о механизме, которого приложение не использует.
Надпись о пустом результате размещена с v-if. Она существует в DOM только при нулевой длине. Сам ul может оставаться пустым: этот вариант допустим и сохраняет простую структуру шаблона. Если позднее панель будет иметь собственное оформление пустого результата, вынесем оболочку в компонент. Важнее сейчас, чтобы пользователь мог увидеть действующие условия и исправить их, а не воспринимал пустую страницу как исчезновение команды.
В поисковом поле нет принудительного изменения query до нижнего регистра. Нормализуется копия для сравнения, а введённая строка сохраняет привычный человеку вид. Такое разделение помогает избежать скачков текста при вводе. Для чекбокса v-model работает с булевым значением, а не со строкой «on», которую может дать обычная отправка HTML-формы. Описание связей формы Vue полезно читать именно с учётом различий элементов.
Граница локального поиска
Это поиск по уже загруженным сотрудникам. Если сервер отдаст только первую страницу из тысячи записей, тот же filter сможет найти совпадения лишь на этой странице. Интерфейс должен будет назвать результат соответствующим образом либо передавать запрос на сервер. Пока договор говорит о небольшой полной команде, поэтому локальный поиск подходит. Не увеличивайте объём клиента автоматически, когда меняется масштаб предметной задачи.
Самостоятельно проследите три комбинации: пустая строка со включённым доступным фильтром, «пав» с выключенным фильтром и «пав» с включённым. Для исходных данных ожидаются соответственно два, один и ноль участников. Затем временно измените available Павла в модели и объясните новый результат каждого выражения. Это проверяет понимание зависимостей лучше, чем механическое повторение кода. Следующий урок добавит сортировку и покажет, почему сохранение идентичности строки важно даже при правильной фильтрации.
Изменение критерия поиска
Если руководитель попросит искать только по имени, изменится формирование text, а не структура members. Это показывает полезную независимость представления: одно и то же исходное поле может участвовать в разных условиях без переписывания записи сотрудника. Для поиска по двум словам текущий includes требует непрерывную подстроку. Запрос «Анна Дизайнер» совпадёт, а перестановка «Дизайнер Анна» нет. Более сложный поиск следует вводить как новое правило, с явно описанными ожидаемыми совпадениями.
Для самостоятельного воспроизведения результата «Получать отфильтрованный список без второго хранилища» выберите lesson-03 в архиве исходников. Статус выполнения этого состояния: not_run.