Практика: список задач с фильтрами

Закрепляем весь курс на практике: собираем список задач с добавлением, удалением, отметкой выполненных и фильтрами — на 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Валидирует форму
Поддержать проект