Даже небольшой скрипт для страницы может быстро превратиться в хаос, если перемешать:
- поиск элементов
- обработчики событий
- изменения DOM
- работу с данными
Простой подход для маленьких проектов - разделить код на 4 части:
- элементы (что в DOM используется)
- состояние (какие данные хранятся)
- действия (обработчики и логика)
- render (обновление интерфейса)
1) Базовый шаблон структуры
const els = {
form: document.querySelector(".form"),
input: document.querySelector(".form input"),
list: document.querySelector(".list"),
counter: document.querySelector(".counter"),
};
const state = {
items: [],
};
function init() {
bindEvents();
render();
}
function bindEvents() {
els.form.addEventListener("submit", onSubmit);
els.list.addEventListener("click", onListClick);
}
function onSubmit(event) {
event.preventDefault();
const value = els.input.value.trim();
if (value.length === 0) return;
addItem(value);
els.input.value = "";
render();
}
function onListClick(event) {
const btn = event.target.closest("button");
if (!btn) return;
if (!els.list.contains(btn)) return;
const id = btn.dataset.id;
if (btn.dataset.action === "remove") {
removeItem(id);
render();
}
}
function addItem(text) {
state.items.push({ id: crypto.randomUUID(), text });
}
function removeItem(id) {
state.items = state.items.filter((x) => x.id !== id);
}
function render() {
renderList();
renderCounter();
}
function renderList() {
els.list.innerHTML = "";
const fragment = document.createDocumentFragment();
state.items.forEach((item) => {
const li = document.createElement("li");
const span = document.createElement("span");
span.textContent = item.text;
const btn = document.createElement("button");
btn.type = "button";
btn.textContent = "Remove";
btn.dataset.action = "remove";
btn.dataset.id = item.id;
li.append(span, btn);
fragment.append(li);
});
els.list.append(fragment);
}
function renderCounter() {
els.counter.textContent = String(state.items.length);
}
init();
В этом примере все сделано максимально простыми средствами, но структура уже помогает держать код читаемым.
2) Что такое state и почему он нужен
state - это один объект, в котором хранится то, что должно “жить” между событиями:
- список элементов
- активный фильтр
- текущая страница
- выбранная тема
Если состояние лежит в одном месте, проще:
- менять логику
- перерисовывать интерфейс
- сохранять данные (например, в localStorage)
3) Что делает render
render - функция, которая приводит DOM к текущему состоянию.
Правило, которое упрощает жизнь:
- обработчик события меняет
state - потом вызывается
render()
Так меньше “случайных” изменений DOM по всему файлу.
4) Разделение render на части
Если UI состоит из нескольких независимых блоков, удобно разделить:
function render() {
renderList();
renderCounter();
renderEmptyState();
}
Так проще читать и править.
5) Мини-правила для читаемого DOM-кода
1) Один обработчик - одно действие
- onSubmit - только добавление
- onListClick - только реакции на клики внутри списка
2) Ранние выходы
- меньше вложенности
- меньше if внутри if
function onSubmit(event) {
event.preventDefault();
const value = els.input.value.trim();
if (value.length === 0) return;
addItem(value);
render();
}
3) Не искать элементы много раз
- поиск через querySelector делать один раз и хранить в
els
4) Чистые функции для данных
- addItem/removeItem меняют данные
- render отвечает за DOM
6) Когда render “перерисовывает все” - это нормально
В маленьких задачах проще и надежнее иногда перерисовать весь список:
- меньше ошибок
- меньше условий
- понятнее структура
Когда появится необходимость оптимизировать, можно будет обновлять только часть DOM. Но начинать обычно лучше с понятного решения.
7) Связка с localStorage
Если состояние нужно сохранять, добавляется 2 функции:
- загрузка при старте
- сохранение после изменений
function loadState() {
const raw = localStorage.getItem("app:items");
if (!raw) return;
try {
state.items = JSON.parse(raw);
} catch (e) {
state.items = [];
}
}
function saveState() {
localStorage.setItem("app:items", JSON.stringify(state.items));
}
И дальше в обработчиках:
addItem(value);
saveState();
render();
Что стоит запомнить
- Для маленьких DOM-скриптов помогает схема: els + state + actions + render.
- Обработчики меняют состояние и вызывают render.
- render приводит интерфейс к состоянию и может быть разбит на несколько функций.
- Ранние выходы и небольшие функции заметно улучшают читаемость.
- При необходимости состояние удобно сохранять в localStorage.