Skip to content

Ограничения

Хотя большинство библиотек для роутинга предпочитает копировать весь функционал, который есть у конкурентов, и жестко привязывать к своей экосистеме, Reactive Route сохраняет фокус на:

  • реактивном хранении данных о текущей странице в router.state[name]
  • асинхронной подготовке к открытию следующей страницы
  • удобном интерфейсе для редиректов (т.к. нативный window.pushState('/new-path?phone=1234') не типизирован, выполняется синхронно и не работает на сервере или в виджетах)
  • независимости от фреймворка и системы реактивности

Позитивным трендом сейчас является TypeScript, поэтому весь функционал, не совместимый со статической типизацией, отсутствует. Сюда входят опциональные параметры в пути, JSX-объявление <Route path="untyped[?-partial]-string/:id/:id/:id">, файловый роутинг posts/$postId/$/$.tsx и другие практики, разрушающие типобезопасность и структуру.

Nested routes / Динамические роуты

Представьте такую валидную с точки зрения ряда альтернативных роутеров конфигурацию, но которой никогда не будет в Reactive Route:

ts
const configs = createConfigs({
  user: {
    path: '/user/:id',
    params: { id: validators.numeric },
    
    // imagine we have a nested route here
    children: {
      default: {
        // :id collision with parent
        path: 'default/:id',
        
        // validator schema collision with parent
        params: { id: validators.notNumeric },
        loader: () => import('./pages/user'),
      },
      
      // name collision with parent
      user: {
        // :id collision with parent
        path: 'view/:id',
        
        // validator schema collision with parent
        params: { id: validators.alphaNumeric },
        loader: () => import('./pages/user/view'),
      }
    }
  },
});

// try to extend the configs
configs.extend({
  parent: 'user.user',
  children: {
    default: {
      // path collision with parent path: 'view/:id'
      path: 'default/:id',

      // validator schema collision with parent
      params: { id: validators.notNumeric },

      // component collision with parent
      loader: () => import('./pages/user'),
    }
  }
}

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

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

Также крайне сложно сделать стабильный поток загрузки данных и проверки прав пользователя. Будет ли вызван beforeEnter второго уровня при изменении params или query третьего уровня, и наоборот?

Разумеется, страдает и DX: отсутствует поддержка "Find Usages" или быстрой навигации в IDE, ограниченная поддержка автодополнения, отсутствуют подсказки при описании редиректов, а при использовании ИИ при рефакторинге требуется передавать весь код проекта, так как структура роутов строится в рантайме.

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

Hash / History State

В Reactive Route не поддерживаются URL hash и History State, и принудительно очищаются при редиректах. Их использование усложняет проектирование и делает состояние приложения фрагментированным.

В библиотеке есть два мощных механизма для динамических параметров — опциональные query и обязательные params. Они типизированы, участвуют в жизненных циклах, валидируются и способны эффективно решать задачи, для которых традиционно использовался hash.

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

Нестроковые params и query

Так как браузерный URL содержит только строковые значения, в Reactive Route нет утилит для автоматической конвертации в разные типы данных.

ts
const configs = createConfigs({
  user: {
    path: '/user/:id',
    loader: () => import('./pages/user'),
    
    // validation is required and stable
    params: { id: validators.numeric },
    query: { phone: validators.numeric.length(6, 15) },
  },
});

// 100% safe casts

const { params, query } = router.state.user!;

const id = computed(() => Number(params.id));
const phone = computed(() => query.phone ? Number(query.phone) : null);

// URL can't have numbers, only strings.
// No hidden magic of type conversion

router.redirect({
  name: 'user',
  params: { id: '9999' },
  query: { phone: '123456' }
})

В этом примере строковые значения проверяются с помощью validators.numeric, который либо специфичен для проекта, либо берется из любой из сотен библиотек для валидации. Подразумевается, что он уже проверил значение на NaN, Infinite, -0 и подтвердил, что строка при Number(params.id) является корректным числом.

Но само приведение к Number / Boolean / Object / Array не встроено в библиотеку и как в примере выше является ответственностью разработчика. Это позволяет использовать структуры любой сложности через собственные механизмы десериализации.

Ряд библиотек имеет встроенные утилиты для приведения значений к определенному типу, однако это создает иллюзию, что URL может хранить и обрабатывать не только строки. Подходы к валидации становятся некорректными, что особенно заметно при попытке возложить на роутер автоматическое приведение к Object / Array / Date и парсинг сложных структур.

Нетипизированные beforeEnter / beforeLeave

TypeScript 5 пока не умеет рекурсивно выводить типы для beforeEnter и beforeLeave, поэтому аргументы currentState, nextState и redirect имеют упрощенные типы. Описывать логику в них нужно осторожно — при рефакторинге TS не подсветит ошибок.

Это ограничение касается только жизненного цикла, во всех остальных сценариях сохраняется полная и строгая типизация. На этот компромисс пришлось пойти, так как выделение функций жизненного цикла за пределы createConfigs приводит к расползанию логики работы со страницами и ухудшает DX.

No AI participated in the development. MIT License