Learn JavaScript

Как структурировать маленький DOM-код: функции и render

Даже небольшой скрипт для страницы может быстро превратиться в хаос, если перемешать:

  • поиск элементов
  • обработчики событий
  • изменения 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.