Проект: список задач — план и разметка
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
Проект начинается не с кода, а с трёх решений: что приложение умеет, из каких файлов состоит и как выглядит одна задача — например, { id, title, done }. Разберёмся с ними сейчас, чтобы дальше оставалось только печатать.
Задание звучит скучно: «сделать список дел». Пока оно сформулировано так, делать можно что угодно — и через час выяснится, что кнопка получилась красивая, а задачи всё ещё не добавляются. Поэтому первый шаг — превратить идею в список требований, где каждое можно проверить руками.
| Что умеет | Что это значит на практике |
|---|---|
| Добавить задачу | текст из поля попадает в список, поле очищается |
| Отметить выполненной | клик по чекбоксу меняет флаг и вид строки |
| Удалить задачу | кнопка рядом со строкой убирает задачу насовсем |
| Пережить перезагрузку | при старте список читается из хранилища браузера |
| Показать только нужные | переключатель «все / активные / сделанные» |
Первые три строки — это MVP, минимум, после которого приложением уже можно пользоваться. Две последние дописываются поверх работающего кода, а не вместо него. Отметим это прямо в коде — так виднее, где сегодняшняя граница.
Требования отвечают на вопрос «что», шаги — на вопрос «в каком порядке». Хороший шаг заканчивается чем-то проверяемым: открыл страницу — увидел результат. Если после шага смотреть не на что, шаг выбран неудачно.
Порядок не случаен: каждый следующий шаг опирается на предыдущий. Нельзя нарисовать список, пока не решено, из чего он состоит, и нечего добавлять, пока нечем показать добавленное. Шаги «хранилище» и «фильтры» встанут в конец очереди — они меняют только источник данных и то, какую часть массива мы рисуем.
Не садитесь писать код, пока не можете назвать шаг, который делаете прямо сейчас. Если название расплывается — шаг слишком большой, поделите его надвое.
HTML для проекта нужен скромный: форма с полем ввода и кнопкой, а под ней пустой <ul>. Ни одной <li> руками не пишем — все строки создаёт JavaScript. Иначе тестовая задача «ааа проверка» однажды доедет до живого сайта.
Каждой строке нужен опознавательный знак — атрибут data-id. По нему обработчик поймёт, какую именно задачу отметили или удалили: не по тексту (тексты повторяются) и не по позиции (позиции сдвигаются).
<form> бесплатно даёт две вещи: Enter в поле отправляет её, а атрибут required не пропустит пустую задачу. Взамен в обработчике придётся вызвать event.preventDefault() — без него браузер по привычке перезагрузит страницу, и список исчезнет на глазах.
Кнопке «Добавить» ничего дописывать не нужно: <button> без атрибута type внутри формы по умолчанию её отправляет, а кликабельный <div> на том же месте форму не отправил бы. Чем настоящая кнопка отличается от самодельной, разобрано в прологе, в статье про кнопки, ссылки и подписи.
Файлов будет три: index.html, style.css, app.js. Сборщики, модули и папка src появятся в других проектах — здесь они добавили бы шагов, но не понимания.
| Файл | Что внутри |
|---|---|
| index.html | каркас: форма, пустой список, подключение стилей и скрипта |
| style.css | внешний вид: отступы, зачёркнутый текст у выполненных задач |
| app.js | данные и логика: массив задач, отрисовка, обработчики событий |
Начинается index.html со скелета, общего для любой страницы: doctype, служебный <head> с кодировкой и названием вкладки и видимый <body>. Зачем нужна каждая его строка, разобрано в прологе, в статье про скелет страницы.
Скрипт подключаем с атрибутом defer: браузер скачает файл параллельно с разметкой, а выполнит, когда страница уже собрана. Так код не будет искать список, которого ещё нет. Внутри app.js — одно состояние и одна функция запуска.
Самое важное решение проекта — как выглядит одна задача. Договоримся: это объект с тремя полями. id — число, уникальное и неизменное; title — строка; done — true или false, и никаких «да», «нет» и пустых строк.
Весь список — массив таких объектов, и он единственный источник правды. Экран лишь отражает массив: поменяли данные — перерисовали. Обратное направление, когда состояние вычитывают из текста строк на странице, работает ровно до первой задачи с названием «Удалить».
Индекс — это адрес в очереди, а не имя. Стоит удалить одну задачу, и все следующие переезжают: обработчик, запомнивший «вторая строка», начинает попадать не туда.
- Сначала требования и порядок шагов, потом код: план ловит ошибки дешевле, чем отладка.
- MVP проекта — добавить, отметить, удалить; хранение и фильтры дописываются поверх работающего.
- Разметка минимальная: форма с полем и кнопкой плюс пустой список; строки создаёт JavaScript, у каждой есть data-id.
- Три файла — index.html, style.css, app.js; скрипт подключается с defer, чтобы код работал с готовой страницей.
- Задача — объект { id, title, done }, а массив таких объектов и есть единственный источник правды.
1/3Почему у задачи заводят отдельное поле id, а не пользуются её местом в массиве?
2/3Что выведет console.log(JSON.stringify({ id: 1, done: false }));?
3/3Где в нашем проекте хранится состояние — сам список задач?
Опишите одну задачу
Напишите функцию makeTask(title): она возвращает объект задачи с полями id, title и done. Идентификаторы выдаются по порядку начиная с единицы, лишние пробелы вокруг названия обрезаются, новая задача всегда невыполненная.
Счётчик lastId живёт снаружи функции: объяви его внутри — он обнулялся бы при каждом вызове, и все задачи получили бы id, равный единице.
Обрезка через trim() делается один раз, при создании: дальше по проекту можно считать, что в title лежит уже чистая строка.
Строка статуса
По массиву задач соберите строку вида «Осталось: 1 из 2» — сколько задач ещё не выполнено и сколько их всего. Функция должна работать с любым массивом такого формата.
Невыполненные отбираем фильтром, общее число берём из длины массива.
На пустом списке получится «Осталось: 0 из 0» — на этом месте позже появится подсказка «Пока пусто», и функция статуса просто не будет вызываться.