Кастомизация контекста

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


Определение контекста

Контекст в Universal Router — это объект, который передаётся в каждый обработчик маршрута. Он создаётся при инициализации роутера и доступен через параметр context во всех маршрутах:

import UniversalRouter from 'universal-router';

const routes = [
  {
    path: '/home',
    action: (context) => {
      return `Welcome, ${context.user.name}`;
    }
  }
];

const router = new UniversalRouter(routes, {
  context: {
    user: { name: 'Alice' },
    api: {
      fetchData: () => fetch('/data').then(res => res.json())
    }
  }
});

router.resolve('/home').then(console.log); // Output: Welcome, Alice

Ключевые моменты:

  • Контекст доступен во всех маршрутах.
  • В него можно помещать любые объекты, функции, сервисы или глобальные состояния.
  • Позволяет разделять логику роутера и приложения, делая маршруты более модульными.

Динамическое расширение контекста

Иногда контекст необходимо модифицировать в зависимости от текущего запроса. Для этого Universal Router поддерживает функцию resolveContext, которая позволяет вычислять контекст динамически:

const router = new UniversalRouter(routes, {
  resolveContext: ({ pathname }) => {
    return {
      pathname,
      user: { name: pathname.includes('admin') ? 'Admin' : 'Guest' }
    };
  }
});

router.resolve('/admin/dashboard').then(console.log); // Output зависит от pathname

Преимущества динамического контекста:

  • Возможность подставлять данные на основе URL или других условий.
  • Поддержка многоуровневых приложений с различными ролями пользователей.
  • Легкая интеграция с API или внешними источниками данных прямо в момент маршрутизации.

Наследование и объединение контекста

Universal Router позволяет объединять контексты для вложенных маршрутов. Если родительский маршрут имеет контекст, дочерние маршруты могут его расширять:

const routes = [
  {
    path: '/dashboard',
    action: (context) => `Dashboard for ${context.user.name}`,
    children: [
      {
        path: '/settings',
        action: (context) => `Settings for ${context.user.name}, theme: ${context.theme}`
      }
    ]
  }
];

const router = new UniversalRouter(routes, {
  context: {
    user: { name: 'Bob' },
    theme: 'dark'
  }
});

router.resolve('/dashboard/settings').then(console.log); 
// Output: Settings for Bob, theme: dark

Особенности объединения контекста:

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

Использование сервисов в контексте

Контекст удобно использовать для передачи сервисов, таких как API-клиенты, хранилища состояния или функции утилит:

const apiService = {
  getUser: (id) => fetch(`/users/${id}`).then(r => r.json())
};

const router = new UniversalRouter(routes, {
  context: { apiService }
});

const routes = [
  {
    path: '/user/:id',
    action: async ({ params, apiService }) => {
      const user = await apiService.getUser(params.id);
      return `User: ${user.name}`;
    }
  }
];

Преимущества:

  • Отделение логики получения данных от маршрутов.
  • Упрощение тестирования — контекст можно легко мокировать.
  • Централизация сервисов для всего приложения.

Контекст и асинхронные маршруты

Universal Router полностью поддерживает асинхронные операции внутри контекста. Контекст может включать асинхронные функции, которые вызываются в момент разрешения маршрута:

const router = new UniversalRouter(routes, {
  context: {
    fetchUserData: async (id) => {
      const response = await fetch(`/api/users/${id}`);
      return await response.json();
    }
  }
});

const routes = [
  {
    path: '/profile/:id',
    action: async ({ params, fetchUserData }) => {
      const user = await fetchUserData(params.id);
      return `Profile: ${user.name}`;
    }
  }
];

Особенности:

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

Контекст как инструмент контроля доступа

Контекст можно использовать для реализации системы авторизации и контроля доступа к маршрутам:

const router = new UniversalRouter(routes, {
  context: {
    user: { role: 'guest' },
    requireRole: (role, context) => {
      if (context.user.role !== role) {
        throw new Error('Access denied');
      }
    }
  }
});

const routes = [
  {
    path: '/admin',
    action: ({ requireRole, context }) => {
      requireRole('admin', context);
      return 'Admin panel';
    }
  }
];

Преимущества:

  • Безопасная проверка ролей и прав пользователя.
  • Возможность централизованного управления доступом через контекст.
  • Легкая интеграция с middleware-логикой и другими сервисами.

Контекст в Universal Router является универсальным инструментом для передачи данных, сервисов и функций между маршрутизатором и обработчиками маршрутов. Его кастомизация открывает возможности для динамической маршрутизации, внедрения сервисов, контроля доступа и построения сложных многоуровневых приложений.