Эта статья — про то, как спокойно и осознанно писать код интерфейса. Не быстро, не «в лоб», а так, чтобы через полгода в него было не страшно вернуться.
Мы не будем использовать фреймворки. Не потому что они плохие, а потому что без них хорошо видно основу: состояние, ответственность и границы.
Почему вообще стоит говорить о проектировании
Большинство проблем в UI-коде появляются не из-за JavaScript, а из-за того, что:
- состояние разбросано по коду
- DOM используется как источник истины
- логика и отображение перемешаны
- компонент невозможно контролировать извне
В маленьких примерах это не заметно.
В реальных проектах — очень больно.
Реальная задача
Мы делаем уведомление:
- показывает текст
- закрывается по кнопке
- может закрываться из кода
- не ломает остальной интерфейс
Это простой компонент, но достаточно показательный.
Как обычно пишут такой код
Почти всегда начинают с DOM:
const el = document.querySelector('.notification');
el.querySelector('.close').addEventListener('click', () => {
el.style.display = 'none';
});
Почему это кажется нормальным:
- код короткий
- результат виден сразу
Почему это плохо:
- уведомление жёстко привязано к HTML
- нельзя создать второе уведомление
- невозможно управлять из JS
- состояние нигде не описано
Первый важный шаг — подумать о состоянии
Перед кодом всегда стоит задать вопрос:
В нашем случае всё просто:
- уведомление либо видно
- либо закрыто
Это состояние:
- должно храниться внутри компонента
- не должно быть доступно напрямую
Если состояние открыто — его обязательно кто-нибудь сломает.
Очень важная мысль: DOM — не состояние
Частая ошибка — считать, что:
element.style.display = 'none';
и есть состояние.
Это не так.
DOM — это только отражение состояния.
Если использовать его как источник истины:
- код становится хрупким
- поведение сложно расширять
- появляются странные баги
Правильный подход — сначала состояние, потом DOM.
Начинаем с фабрики компонента
Компонент создаётся функцией.
function createNotification(message) {
let isVisible = true;
Что здесь происходит:
- isVisible — внутреннее состояние
- оно приватное
- живёт столько же, сколько компонент
Никакой глобальной области, никакого window.
Создаём DOM-структуру
Теперь создаём элементы.
const element = document.createElement('div');
element.className = 'notification';
const text = document.createElement('span');
text.textContent = message;
const closeButton = document.createElement('button');
closeButton.textContent = '×';
element.appendChild(text);
element.appendChild(closeButton);
Важно:
- мы не вставляем элемент в DOM
- компонент ещё не «живёт»
- нет побочных эффектов
Это делает код предсказуемым и тестируемым.
Обновление интерфейса — отдельная логика
Теперь ключевая часть — render.
function render() {
element.style.display = isVisible ? 'block' : 'none';
}
Это очень простой код, но он задаёт правильное мышление:
Мы не прячем элемент в обработчике клика.
Мы меняем состояние и перерисовываем интерфейс.
Управление состоянием
function close() {
if (!isVisible) return;
isVisible = false;
render();
}
Почему так лучше:
- состояние меняется в одном месте
- невозможно закрыть уведомление дважды
- поведение легко расширить
Например, добавить анимацию, таймер или колбэк.
События — внутренняя деталь компонента
closeButton.addEventListener('click', close);
Обрати внимание:
- внешний код не знает про кнопку
- не знает про DOM-структуру
- не знает про логику закрытия
Это инкапсуляция в чистом виде.
Инициализация компонента
render();
Компонент сам приводит себя в корректное состояние.
Внешний код не обязан помнить, что нужно что-то вызвать.
Возвращаем API, а не реализацию
return {
element,
close
};
}
Это одна из самых важных строк статьи.
Снаружи доступно только:
- element — чтобы вставить компонент
- close() — бизнес-действие
Нельзя:
- изменить isVisible
- тронуть внутренние элементы
- сломать логику обновления
Это делает компонент надёжным.
Использование компонента
const notification = createNotification('Saved!');
document.body.appendChild(notification.element);
Или программное управление:
setTimeout(() => {
notification.close();
}, 3000);
Почему это правильная практика
Даже в этом маленьком примере мы видим:
- чёткую ответственность
- изолированное состояние
- понятный жизненный цикл
- простой и честный API
Если завтра потребуется:
- добавить автозакрытие
- сделать разные типы уведомлений
- добавить очередь
код не придётся выбрасывать.
Почему так делают фреймворки
React, Vue и другие решают те же проблемы:
- где хранить состояние
- кто имеет к нему доступ
- как обновлять интерфейс
- как защититься от ошибок
Фреймворки автоматизируют это.
Но если не понимать основу, они превращаются в магию.
Главный вывод
Хороший UI-компонент:
- начинается с понимания состояния
- не использует DOM как источник истины
- имеет чёткие границы
- сложно использовать неправильно
Если этот подход усвоен, дальше уже не важно — пишешь ли ты на чистом JS или с фреймворком.
08.02.2026
0
34
Комментарии
0