Практика: список задач с фильтрами
Закрепляем весь курс на практике: собираем список задач с добавлением, удалением, отметкой выполненных и фильтрами — на ref, computed, v-for и v-model.
Todo-приложение — классический проект, который связывает воедино реактивное состояние, списки, формы и вычисляемые свойства. Соберём его шаг за шагом.
Состояние и добавление
Нужны список задач и текст нового ввода. Добавление создаёт объект с уникальным id (важно для :key из раздела 2) и очищает поле:
<script setup>
import { ref, computed } from 'vue'
const todos = ref([])
const newText = ref('')
const filter = ref('all') // all | active | done
let nextId = 1
function addTodo() {
const text = newText.value.trim()
if (!text) return // не добавляем пустые
todos.value.push({ id: nextId++, text, done: false })
newText.value = ''
}
function removeTodo(id) {
todos.value = todos.value.filter(t => t.id !== id)
}
</script>
Фильтрация через computed
Список под фильтр — производное значение, поэтому это computed (раздел 3). Он сам пересчитается при смене фильтра или задач. И счётчик активных тоже:
<script setup>
// ...продолжение
const visibleTodos = computed(() => {
if (filter.value === 'active') return todos.value.filter(t => !t.done)
if (filter.value === 'done') return todos.value.filter(t => t.done)
return todos.value
})
const activeCount = computed(() => todos.value.filter(t => !t.done).length)
</script>
Шаблон
Форма добавления через v-model и @submit.prevent, список через v-for, переключение выполнения через v-model на чекбоксе, динамический класс на выполненной задаче:
<template>
<form @submit.prevent="addTodo">
<input v-model="newText" placeholder="Новая задача">
<button>Добавить</button>
</form>
<div>
<button @click="filter = 'all'">Все</button>
<button @click="filter = 'active'">Активные</button>
<button @click="filter = 'done'">Завершённые</button>
</div>
<ul>
<li v-for="todo in visibleTodos" :key="todo.id" :class="{ done: todo.done }">
<input type="checkbox" v-model="todo.done">
{{ todo.text }}
<button @click="removeTodo(todo.id)">✕</button>
</li>
</ul>
<p>Осталось активных: {{ activeCount }}</p>
</template>
<style scoped>
.done { text-decoration: line-through; color: #999; }
</style>
Проверим логику фильтра отдельно
Сердце приложения — фильтрация и подсчёт. Прогоним её обычным кодом, чтобы убедиться в корректности:
const todos = [
{ id: 1, text: "Купить хлеб", done: true },
{ id: 2, text: "Позвонить маме", done: false },
{ id: 3, text: "Прочитать главу", done: false },
];
function visible(list, filter) {
if (filter === "active") return list.filter(t => !t.done);
if (filter === "done") return list.filter(t => t.done);
return list;
}
const activeCount = todos.filter(t => !t.done).length;
console.log("Все:", visible(todos, "all").length);
console.log("Активные:", visible(todos, "active").map(t => t.text));
console.log("Завершённые:", visible(todos, "done").map(t => t.text));
console.log("Осталось активных:", activeCount);
Вывод:
Все: 3 Активные: [ 'Позвонить маме', 'Прочитать главу' ] Завершённые: [ 'Купить хлеб' ] Осталось активных: 2
Какие приёмы здесь использованы
| Приём | Где в проекте |
ref | список задач, поле ввода, фильтр |
computed | видимый список и счётчик активных |
v-for + :key | вывод задач |
v-model | поле ввода и чекбокс |
:class | зачёркивание выполненной |
Итог
- Todo-приложение связывает
ref,computed,v-for,v-modelи:class. - Фильтрованный список — это
computed, который пересчитывается сам. - У каждой задачи уникальный
id— он нужен для:keyи удаления. @submit.preventотменяет перезагрузку страницы формой.
Проверьте себя
1. Почему отфильтрованный список задач лучше делать через computed, а не обычной переменной?
Acomputed работает только со строками
Bcomputed автоматически пересчитывается при изменении фильтра или списка задач и кешируется
CОбычная переменная не может хранить массив
DТак требует v-for
2. Зачем каждой задаче давать уникальный id?
AДля красоты
BЧтобы использовать его в :key для v-for и точно удалять нужную задачу по id
Cid обязателен для v-model
DЧтобы задачи сортировались автоматически
3. Что делает модификатор .prevent в @submit.prevent на форме?
AЗапрещает вводить текст
BОтменяет стандартную перезагрузку страницы при отправке формы
CОчищает поле ввода
DВалидирует форму