Когда код растет, главная проблема - не синтаксис, а “где что лежит”. Если перемешать работу с данными, логику и DOM, появляется эффект “трогаешь одно - ломается другое”.
Один из самых простых и рабочих подходов для небольших проектов - разделить код на три слоя:
- data - получение и хранение данных
- logic - правила и вычисления
- ui - работа с DOM и отображение
1) Что относится к data
Слой data отвечает за:
- загрузку данных (fetch)
- сохранение/чтение (localStorage)
- преобразование “сырого ответа” в удобный формат (если нужно)
- кеширование
data не должен знать про DOM.
Примеры функций data:
loadJSON(url)saveSettings(settings)loadSettings()loadWithCache(url, options)
2) Что относится к logic
Слой logic отвечает за:
- чистые вычисления
- правила (фильтрация, сортировка, валидация)
- подготовку данных к отображению
logic не должен:
- читать DOM
- менять DOM
- делать fetch
- писать в localStorage
Примеры функций logic:
formatPrice(value)filterActive(items)buildViewModel(data)
3) Что относится к ui
Слой ui отвечает за:
- поиск элементов (querySelector)
- обработку событий (addEventListener)
- обновление разметки (render)
- отображение ошибок и загрузки
ui не должен:
- держать “правила” внутри обработчиков
- вычислять сложные вещи
- смешивать сетевую логику с DOM-логикой в одной функции
4) Простое правило зависимостей
Чтобы структура не превратилась в хаос, помогает правило направленности:
uiможет импортироватьlogicиdatalogicможет импортировать маленькие утилиты (внутри logic), но неuiи неdatadataможет импортировать утилиты (например,safeJSONParse), но неuiи неlogic
Если logic начнет импортировать ui, получится круговая зависимость и ломается читаемость.
5) Шаблон структуры папок
Один из вариантов:
src/
main.js
data/
api.js
storage.js
cache.js
logic/
calc.js
format.js
ui/
dom.js
render.js
Это не “единственно верно”, но дает понятный ориентир.
6) Мини-пример: список товаров с фильтром
Ниже пример того же функционала, но разделенного по слоям.
data/api.js
export async function loadJSON(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error("HTTP " + response.status);
}
return response.json();
}
logic/format.js
export function formatPrice(value) {
return value.toFixed(2);
}
logic/filter.js
export function filterByMinPrice(items, minPrice) {
return items.filter((item) => item.price >= minPrice);
}
ui/render.js
import { formatPrice } from "../logic/format.js";
export function renderList(root, items) {
root.innerHTML = "";
for (const item of items) {
const li = document.createElement("li");
li.textContent = item.title + " - " + formatPrice(item.price);
root.append(li);
}
}
export function renderError(root, message) {
root.textContent = message;
}
main.js (точка входа)
import { loadJSON } from "./data/api.js";
import { filterByMinPrice } from "./logic/filter.js";
import { renderList, renderError } from "./ui/render.js";
const listEl = document.querySelector("#list");
const inputEl = document.querySelector("#minPrice");
const reloadBtn = document.querySelector("#reload");
let items = [];
async function loadAndRender() {
try {
items = await loadJSON("/data/items.json");
render();
} catch (e) {
renderError(listEl, "Load failed");
}
}
function render() {
const minPrice = Number(inputEl.value) || 0;
const filtered = filterByMinPrice(items, minPrice);
renderList(listEl, filtered);
}
inputEl.addEventListener("input", render);
reloadBtn.addEventListener("click", loadAndRender);
loadAndRender();
Что видно по структуре:
main.jsсобирает проект: получает DOM-элементы, подписывается на события, связывает data -> logic -> ui- data занимается только запросами
- logic только фильтрует и форматирует
- ui только рисует
7) Как понять, что файл “не на своем месте”
Несколько быстрых сигналов:
- файл ui внезапно содержит сложные вычисления и десятки строк условий
- функция logic читает
document.querySelector - функция data возвращает готовый HTML
- обработчик события делает fetch, парсинг, фильтрацию, сортировку и render в одном месте
В таких случаях код обычно проще разделить на маленькие функции и вынести по слоям.
Что стоит запомнить
- data - про загрузку и хранение, logic - про правила и вычисления, ui - про DOM.
- Направление зависимостей важно: ui -> logic -> utils, ui -> data -> utils.
- Точка входа (main) склеивает слои и управляет потоком.
- Такой подход помогает быстрее искать ошибки и легче дополнять проект.