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

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

Базовый подход к компонентам

В Universal Router маршрут определяется объектом с ключами path и action. Значение action часто является функцией, которая возвращает компонент или результат его рендеринга. Для переиспользования компонентов важно строить маршруты так, чтобы один и тот же компонент мог обслуживать несколько маршрутов с разными параметрами.

import UniversalRouter from 'universal-router';
import HomePage from './components/HomePage';
import UserPage from './components/UserPage';

const routes = [
  { path: '/', action: () => HomePage() },
  { path: '/user/:id', action: ({ params }) => UserPage(params) }
];

const router = new UniversalRouter(routes);

В этом примере UserPage может быть использован для любых пользователей, благодаря параметру id. Такой подход позволяет не дублировать логику отображения, а лишь передавать разные данные через параметры.

Передача контекста и данных

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

const routes = [
  {
    path: '/dashboard',
    action: (context) => Dashboard(context)
  },
  {
    path: '/settings',
    action: (context) => Settings(context)
  }
];

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

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

Вложенные маршруты и композиция

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

const routes = [
  {
    path: '/admin',
    action: ({ next }) => AdminLayout({ content: next() }),
    children: [
      { path: '/users', action: () => UsersPage() },
      { path: '/reports', action: () => ReportsPage() }
    ]
  }
];

Здесь AdminLayout используется для всех дочерних маршрутов /admin/*, а конкретный контент подставляется через вызов next(). Такой подход позволяет разделить структуру страницы и контент, делая компоненты максимально универсальными.

Использование одного компонента для нескольких маршрутов

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

const routes = [
  {
    path: ['/search', '/search/:query'],
    action: ({ params }) => SearchPage(params)
  }
];

В этом примере SearchPage будет использоваться как для /search, так и для /search/something, что сокращает количество компонентов и упрощает поддержку.

Lazy Loading компонентов

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

const routes = [
  {
    path: '/profile',
    action: async () => {
      const { default: ProfilePage } = await import('./components/ProfilePage');
      return ProfilePage();
    }
  }
];

Асинхронная загрузка позволяет использовать один и тот же компонент в разных маршрутах без увеличения начального объёма бандла.

Создание обобщённых компонентов

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

function ItemPage({ type, id }) {
  return fetchItemData(type, id).then(data => renderItem(data));
}

const routes = [
  { path: '/products/:id', action: ({ params }) => ItemPage({ type: 'product', ...params }) },
  { path: '/articles/:id', action: ({ params }) => ItemPage({ type: 'article', ...params }) }
];

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

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

Universal Router поддерживает middleware через вложенные вызовы next(). Это позволяет переиспользовать общую логику для нескольких маршрутов, например, авторизацию, логирование или предзагрузку данных.

const authMiddleware = async (context, next) => {
  if (!context.user) throw new Error('Unauthorized');
  return next();
};

const routes = [
  {
    path: '/dashboard',
    action: authMiddleware,
    children: [
      { path: '/', action: () => DashboardPage() }
    ]
  }
];

Middleware можно применять к любым компонентам, обеспечивая повторное использование функционала без изменения самих компонентов.

Итоговые принципы переиспользования компонентов

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

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