Многие думают, что для создания современного одностраничного приложения (SPA) обязательно нужны React, Vue или Angular. Но что, если я скажу, что всё это можно сделать на чистом JavaScript, без единой библиотеки? И это будет не только просто, но и полезно для понимания того, как всё работает под капотом. В этой статье мы разберём, как построить SPA на нативном JS, и вы увидите, что это не так страшно, как кажется.

Digital-студия WNDER

Что такое SPA и зачем он нужен?

SPA (Single Page Application) — это веб-приложение, которое загружает одну HTML-страницу и динамически обновляет её содержимое при взаимодействии с пользователем. Вместо того чтобы каждый раз запрашивать новую страницу с сервера, SPA подгружает только необходимые данные и перерисовывает часть интерфейса. Это делает работу приложения быстрее и плавнее, как в нативных программах.

Представьте, что вы листаете ленту в социальной сети. Если бы каждая прокрутка вызывала полную перезагрузку страницы, вы бы сошли с ума. SPA решает эту проблему: контент подгружается незаметно, а вы продолжаете скроллить.

Основные компоненты SPA на чистом JavaScript

Чтобы построить SPA, нужно реализовать несколько ключевых механизмов:

  • Роутинг — переключение между «страницами» без перезагрузки. Обычно используется хэш (`#`) или History API.
  • Шаблонизация — динамическое создание HTML-элементов на основе данных.
  • Управление состоянием — хранение данных приложения и их обновление.
  • Обработка событий — реакция на действия пользователя.

Всё это можно сделать с помощью встроенных возможностей браузера: `window.location`, `history.pushState`, `document.createElement`, `addEventListener` и других.

Пример простого SPA на нативном JavaScript

Давайте создадим мини-приложение с двумя «страницами»: главная и о нас. Для навигации используем хэш.

Сначала HTML-каркас:

<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <title>SPA на чистом JS</title>
</head>
<body>
  <nav>
    <a href="#home">Главная</a>
    <a href="#about">О нас</a>
  </nav>
  <div id="app"></div>
  <script src="app.js"></script>
</body>
</html>

Теперь JavaScript (app.js):

// Функция для отображения контента в зависимости от хэша
function router() {
  const app = document.getElementById('app');
  const hash = window.location.hash || '#home';

  switch(hash) {
    case '#home':
      app.innerHTML = '<h1>Главная страница</h1><p>Добро пожаловать!</p>';
      break;
    case '#about':
      app.innerHTML = '<h1>О нас</h1><p>Мы делаем SPA на чистом JS.</p>';
      break;
    default:
      app.innerHTML = '<h1>404</h1><p>Страница не найдена</p>';
  }
}

// Слушаем изменения хэша
window.addEventListener('hashchange', router);

// Инициализация при загрузке
window.addEventListener('load', router);

Этот код отслеживает изменение хэша и перерисовывает содержимое `#app`. Всё просто, не правда ли?

Правило: Всегда используйте `window.addEventListener('hashchange', ...)` для отслеживания навигации. Не забывайте также обрабатывать первоначальную загрузку страницы, чтобы отобразить правильный контент.

Продвинутый роутинг с History API

Хэш-навигация проста, но выглядит не очень красиво (в URL появляется `#`). History API позволяет менять URL без перезагрузки и без хэша. Вот как это работает:

// Переход на новую страницу
function navigate(path) {
  history.pushState({}, '', path);
  render(path);
}

// Обработка кнопок назад/вперёд
window.addEventListener('popstate', () => {
  render(window.location.pathname);
});

// Пример использования
document.querySelector('a[href="/about"]').addEventListener('click', (e) => {
  e.preventDefault();
  navigate('/about');
});

Однако при использовании History API сервер должен быть настроен так, чтобы все запросы возвращали один и тот же HTML-файл (index.html). Иначе при обновлении страницы вы получите 404.

Сравнение подходов к роутингу

Выбор между хэшем и History API зависит от требований. Вот краткая таблица:

Критерий Хэш (#) History API
Простота настройки Высокая (не нужен сервер) Требует настройки сервера
Красота URL С #, менее красиво Чистые URL
SEO Плохо индексируется Лучше, но требует SSR
Поддержка браузерами Везде Современные браузеры

Управление состоянием и шаблонизация

В SPA важно хранить данные и обновлять интерфейс при их изменении. Можно использовать простой объект и функцию рендеринга, которая перерисовывает компоненты при изменении состояния.

const state = {
  items: ['Яблоко', 'Банан', 'Апельсин']
};

function renderList() {
  const list = document.getElementById('list');
  list.innerHTML = state.items.map(item => `<li>${item}</li>`).join('');
}

// Добавление элемента
function addItem(item) {
  state.items.push(item);
  renderList();
}

// Инициализация
renderList();

Для более сложных приложений можно применять паттерн «наблюдатель» или использовать Proxy для отслеживания изменений.

Лайфхак: Используйте `Proxy` для автоматического обновления интерфейса при изменении состояния. Это позволит избежать ручного вызова рендера после каждого изменения.

Заключение

Создание SPA на нативном JavaScript — это не только полезный опыт, но и отличный способ понять, как работают современные фреймворки. Вы можете начать с простого хэш-роутинга, затем перейти на History API, добавить управление состоянием и шаблонизацию. Главное — не бояться экспериментировать и писать код.

В итоге вы получите лёгкое, быстрое и понятное приложение, которое не зависит от сторонних библиотек. А знания, полученные в процессе, пригодятся вам при работе с любыми инструментами.

Студия WNDER