Очистка состояния

Библиотека Page.js — это небольшой клиентский роутер для одностраничных приложений на JavaScript, который обеспечивает управление URL и обработку маршрутов без перезагрузки страницы. Одним из ключевых аспектов построения SPA является корректная очистка состояния между переходами по маршрутам, чтобы не возникало конфликтов данных и утечек памяти.

При переходе между маршрутами важно учитывать следующие элементы состояния:

  • Данные компонентов: значения форм, списки, выбранные элементы.
  • DOM-элементы: временные контейнеры, динамически создаваемые элементы.
  • Подписки и таймеры: события, слушатели, интервалы, таймеры, которые необходимо очищать, чтобы избежать утечек памяти.

Использование middleware для очистки

Page.js поддерживает концепцию middleware, что позволяет централизованно обрабатывать очистку перед переходом на новый маршрут. Пример:

function clearState(ctx, next) {
    if (window.currentComponent && typeof window.currentComponent.destroy === 'function') {
        window.currentComponent.destroy();
    }
    next();
}

page('*', clearState);

Здесь clearState вызывается перед каждым маршрутом ('*'), проверяет наличие текущего компонента и вызывает его метод destroy(). Метод destroy должен содержать логику очистки: удаление DOM-элементов, отмену подписок и остановку таймеров.


Очистка DOM-элементов

Если SPA активно использует динамическую вставку HTML, важно удалять старые элементы перед рендером новых:

function renderComponent(container, component) {
    container.innerHTML = '';
    container.appendChild(component.element);
    window.currentComponent = component;
}

Метод innerHTML = '' полностью очищает контейнер, предотвращая накопление устаревших элементов. Хранение ссылки на текущий компонент через window.currentComponent позволяет вызвать его destroy при следующей навигации.


Отмена событий и таймеров

Частой проблемой является оставление слушателей событий и таймеров. Их необходимо удалять:

class MyComponent {
    constructor() {
        this.handleClick = this.handleClick.bind(this);
        document.addEventListener('click', this.handleClick);
        this.timer = setInterval(this.update.bind(this), 1000);
        this.element = document.createElement('div');
    }

    handleClick(event) {
        console.log('Clicked', event.target);
    }

    update() {
        console.log('Updating...');
    }

    destroy() {
        document.removeEventListener('click', this.handleClick);
        clearInterval(this.timer);
        this.element.remove();
    }
}

Вызов destroy() при переходе по маршруту гарантирует, что ни один старый обработчик или таймер не останется активным.


Очистка состояния формы и данных

При работе с формами и данными состояния важно сбрасывать значения:

function clearForm(formElement) {
    formElement.reset();
}

function clearAppState() {
    window.appState = {};
}

Middleware можно комбинировать с рендерингом, чтобы гарантировать, что перед отображением нового маршрута старые данные сбрасываются:

page('/profile', clearState, (ctx) => {
    renderComponent(document.body, new ProfileComponent());
});

Паттерн “один контейнер — один компонент”

Эффективная стратегия очистки состояния — использование единого контейнера для рендеринга текущего компонента. Это упрощает:

  • Очистку DOM (container.innerHTML = '').
  • Контроль текущего компонента (window.currentComponent).
  • Управление событиями и таймерами через метод destroy.

Обработка асинхронных операций

Если компонент использует fetch-запросы или промисы, их нужно контролировать и отменять при смене маршрута:

class AsyncComponent {
    constructor() {
        this.controller = new AbortController();
        this.fetchData();
        this.element = document.createElement('div');
    }

    async fetchData() {
        try {
            const response = await fetch('/api/data', { signal: this.controller.signal });
            this.element.textContent = await response.text();
        } catch (err) {
            if (err.name === 'AbortError') console.log('Fetch aborted');
        }
    }

    destroy() {
        this.controller.abort();
        this.element.remove();
    }
}

Использование AbortController позволяет безопасно прерывать запросы при навигации, не оставляя “висячих” промисов.


Интеграция с глобальным состоянием

Если приложение использует глобальные состояния (например, через объект window.appState или Redux-подобные хранилища), нужно определять чёткую политику очистки:

  • Перед рендером нового маршрута удалять временные данные.
  • Сохранять только те данные, которые необходимы между страницами.
  • Разграничивать данные компонентов и глобальные данные.

Рекомендации по структуре компонентов

  1. Каждый компонент должен иметь метод destroy() — удаление DOM, событий и таймеров.
  2. Хранение ссылки на текущий компонент — для централизованной очистки перед рендером нового маршрута.
  3. Использование middleware — унификация очистки, вызов до перехода по маршруту.
  4. Контроль асинхронных операций — предотвращение утечек промисов и fetch-запросов.
  5. Один контейнер для рендеринга — упрощение управления состоянием DOM и компонентов.

Эти подходы обеспечивают предсказуемое поведение приложения, снижение утечек памяти и отсутствие конфликтов между состояниями разных маршрутов в SPA с использованием Page.js.