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

Digital-студия WNDER

Что такое SPA и почему это удобно

Представьте, что вы приходите в кафе. В обычном сайте — вы каждый раз выходите на улицу, чтобы зайти в другую дверь. В SPA — вы сидите за столиком, а официант приносит вам новые блюда. Вот эта «смена блюд без выхода из-за стола» и есть суть одностраничного приложения.

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

Главные плюсы SPA:

  • скорость — после первой загрузки ресурсы кэшируются, и переходы происходят мгновенно;
  • отзывчивость — интерфейс реагирует на действия пользователя без задержек на перезагрузку;
  • удобство — можно делать сложные интерактивные интерфейсы, как в Google Maps или Trello.

Но есть и минусы: первая загрузка может быть тяжёлой, а для поисковиков SPA — тёмный лес, если не настроить серверный рендеринг. Но об этом позже.

Как устроено SPA изнутри

В основе любого SPA лежат три кита: HTML, CSS и JavaScript. Но есть ещё одна важная деталь — маршрутизация. В обычном сайте адрес меняется, когда вы переходите на другую страницу. В SPA адрес тоже меняется, но страница не перезагружается. Как так?

Секрет в History API. Это специальный механизм браузера, который позволяет менять URL без перезагрузки. Вызываешь метод pushState — и адрес в строке браузера меняется, а страница остаётся как есть. Затем JavaScript смотрит на новый адрес и решает, что показать.

Представьте, что вы переключаете каналы на телевизоре пультом. Канал меняется, но сам телевизор не выключается. Вот и здесь — адрес меняется, но приложение продолжает работать в том же окне.

Кроме маршрутизации, в SPA важно управлять состоянием. Состояние — это все данные, которые приложение хранит в памяти: что пользователь ввёл в форму, какие элементы открыты, какие данные загружены с сервера. Когда состояние меняется, интерфейс должен обновиться. Это называется реактивностью.

В чистом JavaScript реактивность делается вручную: вы вешаете обработчики событий, меняете DOM, следите за тем, чтобы всё было синхронно. В фреймворках типа React или Vue это автоматизировано, но для понимания сути полезно попробовать руками.

Пишем простое SPA на чистом JavaScript

Хватит теории, давайте кодить. Создадим мини-приложение с двумя «страницами»: главная и о нас. Без сервера, просто чтобы понять принцип.

Сначала разметка. У нас будет контейнер, куда мы будем вставлять содержимое страниц, и две ссылки в шапке.

<!-- index.html -->
<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Моё первое SPA</title>
</head>
<body>
  <header>
    <nav>
      <a href="/" data-link>Главная</a>
      <a href="/about" data-link>О нас</a>
    </nav>
  </header>
  <main id="app"></main>

  <script src="app.js"></script>
</body>
</html>

Обратите внимание на атрибут data-link. Мы будем использовать его, чтобы перехватывать клики по ссылкам и не давать браузеру перезагружать страницу.

Теперь JavaScript. Создадим простую маршрутизацию на основе hash — это когда адрес выглядит как index.html#/about. Такой подход проще для начала, потому что не требует настройки сервера.

// app.js
const app = document.getElementById('app');

// Функция, которая возвращает HTML для каждой страницы
function renderPage(route) {
  if (route === '/about') {
    return '

О нас

Мы маленькая команда, которая любит JavaScript.

'; } return '

Главная

Добро пожаловать! Это простое SPA на чистом JS.

'; } // Обработчик изменения hash function handleRouteChange() { const hash = window.location.hash || '#/'; const path = hash.replace('#', ''); app.innerHTML = renderPage(path); } // Слушаем событие изменения hash window.addEventListener('hashchange', handleRouteChange); // Перехватываем клики по ссылкам с data-link document.addEventListener('click', (e) => { if (e.target.matches('[data-link]')) { e.preventDefault(); window.location.hash = e.target.getAttribute('href'); } }); // Первоначальная загрузка handleRouteChange();

Вот и всё. Теперь при клике на ссылку меняется hash, срабатывает событие hashchange, и мы обновляем содержимое контейнера. Никакой перезагрузки страницы.

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

Как работать с данными и состоянием

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

Состояние — это просто объект или массив, который хранит данные. При изменении состояния мы должны обновить интерфейс. В чистом JS это делается вручную.

Вот пример: у нас есть массив задач, и мы выводим его на страницу. Когда пользователь добавляет задачу, мы обновляем массив и вызываем функцию рендера.

// состояние
let todos = ['Изучить JavaScript', 'Написать статью'];

function render() {
  const list = document.getElementById('todo-list');
  list.innerHTML = todos.map(todo => `
  • ${todo}
  • `).join(''); } // добавление новой задачи function addTodo() { const input = document.getElementById('todo-input'); const text = input.value.trim(); if (text) { todos.push(text); input.value = ''; render(); } }

    Здесь мы вручную следим за тем, чтобы после каждого изменения массива вызывался render(). В больших приложениях такая ручная синхронизация становится ошибкоопасной. Поэтому и придумали фреймворки, которые автоматически обновляют интерфейс при изменении состояния.

    Но даже на чистом JS можно организовать состояние более аккуратно. Например, вынести его в отдельный объект с методами, которые уведомляют подписчиков об изменениях. Это называется паттерн Observer.

    Проще говоря, вы создаёте хранилище, которое умеет оповещать компоненты, когда данные изменились. Такой подход лежит в основе Redux и Vuex, только там всё сделано за вас.

    Правило ленивого программиста: не усложняйте. Если ваше приложение — не космический корабль, не тащите тяжёлые фреймворки. Начните с чистого JS, и когда поймёте, что ручная синхронизация начинает бесить, — только тогда берите React или Vue.

    Сравнение подходов: чистый JS, React, Vue

    Когда вы решите перейти на фреймворки, у вас будет выбор. Давайте сравним основные варианты, чтобы вы понимали, что к чему.

    Подход Плюсы Минусы
    Чистый JS Нет зависимостей, полный контроль, легче понять основы Много ручного кода, легко запутаться в больших проектах
    React Компонентный подход, огромное сообщество, виртуальный DOM Кривая обучения, нужно изучать JSX и экосистему
    Vue Проще для новичков, шаблоны похожи на HTML, хорошая документация Меньше вакансий, чем React, но всё равно востребован

    Если вы новичок, я советую начать с чистого JS. Он даст вам фундамент, на котором потом легко освоить любой фреймворк. А если хочется сразу делать что-то серьёзное, но с меньшим порогом входа — присмотритесь к Vue.

    React — это индустриальный стандарт, его учат на курсах, о нём пишут тонны статей. Но не забывайте, что это просто инструмент. Главное — понимать принципы, а они одинаковы везде.

    Лайфхак: когда учитесь новому фреймворку, не читайте документацию от корки до корки. Сначала сделайте простое приложение — список задач или блог. А потом уже углубляйтесь в тонкости. Так вы быстрее почувствуете, как всё работает.

    Куда двигаться дальше

    Теперь, когда вы знаете основы SPA, можно двигаться дальше. Вот несколько направлений, которые стоит изучить:

    • Виртуальный DOM — как React и Vue ускоряют обновление интерфейса;
    • Компонентный подход — как разбивать интерфейс на переиспользуемые куски;
    • Роутинг на сервере — как настроить сервер, чтобы SPA работало с реальными URL, а не с hash;
    • SSR (серверный рендеринг) — как сделать SPA дружелюбным для поисковиков.

    Не пытайтесь объять необъятное. Идите по шагам: сначала поймите, как работает маршрутизация, потом состояние, потом всё остальное.

    Итог

    Одностраничные приложения — это не магия, а просто набор техник, которые позволяют сделать веб-интерфейс быстрым и отзывчивым. Главное — понять принципы: маршрутизация без перезагрузки, управление состоянием и обновление DOM.

    Начните с малого — напишите простое SPA на чистом JavaScript. Поэкспериментируйте с hash-роутингом, добавьте работу с данными. Когда почувствуете уверенность, переходите к фреймворкам. И помните: никакой фреймворк не спасёт, если вы не понимаете, что происходит под капотом.

    Удачи в кодинге!

    Студия WNDER