Сохранение данных в ctx.state

В Page.js объект ctx (контекст) является центральным механизмом передачи данных между маршрутами и промежуточными функциями (middlewares). Одним из самых удобных способов хранения данных, которые должны быть доступны на протяжении цепочки вызовов, является использование свойства ctx.state.

ctx.state представляет собой обычный объект JavaScript, в который можно помещать любые значения, доступные для текущего маршрута и всех промежуточных обработчиков, следующих за ним.


Инициализация и структура ctx.state

При первом обращении к ctx.state можно присвоить ему объект или отдельные свойства. Например:

page('*', (ctx, next) => {
  ctx.state = {}; // инициализация объекта state
  ctx.state.user = null;
  next();
});
  • ctx.state.user будет доступен для всех последующих обработчиков на этом маршруте.
  • Если не инициализировать ctx.state, можно добавлять свойства напрямую: ctx.state.token = '12345';.

Важно понимать, что ctx.state — это отдельный объект для каждого запроса (в случае SPA — для каждой навигации), поэтому данные не пересекаются между разными переходами страницы.


Передача данных между middleware

Использование ctx.state особенно удобно для передачи данных между несколькими промежуточными функциями:

page('/dashboard', loadUser, loadSettings, renderDashboard);

function loadUser(ctx, next) {
  // имитация запроса к серверу
  ctx.state.user = { id: 1, name: 'Алексей' };
  next();
}

function loadSettings(ctx, next) {
  // использование данных из предыдущего middleware
  ctx.state.settings = { theme: 'dark', notifications: true };
  next();
}

function renderDashboard(ctx) {
  console.log(ctx.state.user.name);       // 'Алексей'
  console.log(ctx.state.settings.theme);  // 'dark'
}

Каждый middleware может как создавать, так и модифицировать данные в ctx.state, а финальная функция обработки маршрута использует уже готовый объект с полным набором данных.


Особенности работы с асинхронными функциями

При работе с асинхронными операциями важно корректно использовать await внутри middleware, чтобы данные в ctx.state были готовы к следующей функции:

page('/profile', async (ctx, next) => {
  ctx.state.user = await fetchUserFromAPI();
  await next();
}, renderProfile);

async function renderProfile(ctx) {
  console.log(ctx.state.user); // данные уже загружены
}
  • Любой middleware может быть асинхронным.
  • Следующие функции в цепочке начнут выполнение только после завершения await next().

Структурирование данных в ctx.state

Для больших приложений рекомендуется использовать четкую структуру ctx.state, чтобы избежать конфликтов и пересечений имен:

ctx.state = {
  auth: {
    user: null,
    token: null
  },
  ui: {
    theme: 'light',
    language: 'ru'
  },
  data: {
    posts: [],
    comments: []
  }
};

Такой подход облегчает поддержку и понимание кода, особенно когда цепочка middleware длинная.


Обновление и очистка данных

  • Обновление данных в ctx.state выполняется напрямую:
ctx.state.auth.user = { id: 2, name: 'Мария' };
  • Очистка данных при необходимости:
ctx.state.data.posts = [];
ctx.state.auth.token = null;

В SPA часто требуется сброс ctx.state при выходе пользователя или смене маршрута.


Использование ctx.state для UI-логики

ctx.state может содержать не только данные с сервера, но и локальное состояние интерфейса, например:

ctx.state.ui.isSidebarOpen = true;
ctx.state.ui.activeTab = 'settings';

Это позволяет использовать единый источник состояния, доступный на протяжении всей навигации, и интегрировать его с библиотеками рендеринга, такими как lit-html или React.


Резюме ключевых моментов

  • ctx.state — объект для хранения данных текущего маршрута.
  • Данные доступны для всех последующих middleware и финальной функции.
  • Поддерживает синхронные и асинхронные операции.
  • Рекомендуется структурировать объект для упрощения поддержки.
  • Можно использовать как для серверных данных, так и для локального состояния UI.
  • Данные из ctx.state изолированы для каждого перехода в SPA.

Использование ctx.state делает архитектуру маршрутов более прозрачной, позволяет централизованно управлять данными и упрощает взаимодействие между middleware.