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

В основе маршрутизатора Angular лежит мощный механизм сопоставления URL-адресов, который преобразует URL-адреса и преобразует их в состояния маршрутизатора. Понимание того, как работает этот механизм, важно для реализации сложных сценариев использования.

Эта статья основана на книге Angular Router, которую вы можете найти здесь https://leanpub.com/router. Книга выходит за рамки руководства по началу работы и подробно рассказывает о маршрутизаторе. Мысленная модель, ограничения дизайна и тонкости API - все покрыто. Если вам понравилась статья, загляните в книгу!

Начнем с этой конфигурации.

Во-первых, обратите внимание, что каждый маршрут определяется двумя ключевыми частями:

  • Как он соответствует URL-адресу.
  • Что он делает после сопоставления URL-адреса.

Важно, чтобы вторая проблема, действие, не влияла на соответствие.

Допустим, мы переходим к «/ inbox / 33 / messages / 44.

Вот как работает сопоставление:

Маршрутизатор проходит по предоставленному массиву маршрутов один за другим, проверяя, начинается ли неиспользованная часть URL-адреса с пути маршрута.

Здесь он проверяет, что «/ inbox / 33 / messages / 44» начинается с «: folder». Оно делает. Таким образом, маршрутизатор устанавливает для параметра папки значение «inbox», затем берет дочерние элементы сопоставленного маршрута, оставшуюся часть URL-адреса, равную «33 / messages / 44», и продолжает сопоставление.

Маршрутизатор проверит, что «33 / messages / 44» начинается с «», и это действительно так, поскольку мы интерпретируем каждую строку как начинающуюся с пустой строки. К сожалению, у маршрута нет дочерних элементов, и мы не использовали весь URL. Таким образом, маршрутизатор вернется назад, чтобы попробовать следующий маршрут «path:‘: id ’».

Этот будет работать. Параметр id будет установлен на «33», и, наконец, будет сопоставлен маршрут «messages /: id», а второй параметр id будет установлен на «44».

Возврат

Давайте еще раз проиллюстрируем откат. Если путь, пройденный через конфигурацию, не «потребляет» весь URL-адрес, маршрутизатор возвращается к альтернативному пути.

Допустим, у нас есть такая конфигурация:

При переходе к «/ a / c» маршрутизатор начнет с первого маршрута. URL-адрес «/ a / c» начинается с «path:‘ a ’», поэтому маршрутизатор попытается сопоставить «/ c» с «b». Поскольку он не может этого сделать, он выполнит возврат и сопоставит «/ a» с «: folder», а затем «c» с «c».

В глубину

Маршрутизатор не пытается найти наилучшее совпадение, т. Е. Не имеет представления о специфике. Его устраивает первый, который использует весь URL-адрес.

При переходе к «/ a / b» первый маршрут будет сопоставлен, даже если второй кажется «специфическим».

Подстановочные знаки

Мы видели, что выражения пути могут содержать два типа сегментов:

  • постоянные сегменты (например, путь: «сообщения»)
  • переменные сегменты (например, путь: ‘: folder’)

Используя только эти два, мы можем обрабатывать большинство случаев использования. Однако иногда нам нужен «другой» путь. Маршрут, который будет соответствовать любому предоставленному URL-адресу. Вот что такое групповые маршруты. В приведенном ниже примере у нас есть маршрут с подстановочными знаками «{path:‘ ** ’, component: NotFoundCmp}», который будет соответствовать любому URL-адресу, который мы не смогли сопоставить в противном случае, и активирует NotFoundCmp.

Маршрут с подстановочными знаками будет «поглощать» все сегменты URL, поэтому NotFoundCmp может получить к ним доступ через внедренный ActivatedRoute.

Маршруты с пустым путем

Если вы еще раз посмотрите на нашу конфигурацию, вы увидите, что для некоторых маршрутов путь установлен в пустую строку. Что это значит?

Устанавливая «путь» в пустую строку, мы создаем маршрут, который создает экземпляр компонента, но не «потребляет» какие-либо сегменты URL. Это означает, что если мы перейдем к «/ inbox», маршрутизатор сделает следующее:

Сначала он проверит, что «/ inbox» начинается с «: folder», что и делает. Таким образом, он возьмет то, что осталось от URL-адреса, то есть «», и дочерние элементы маршрута. Затем он проверит, что «» начинается с «», что он и делает! Итак, результатом всего этого является следующее состояние маршрутизатора:

Маршруты с пустыми путями могут иметь потомков и в целом вести себя как обычные маршруты. Единственная особенность в них - это то, что они наследуют матричные параметры своих родителей. Это означает, что этот URL-адрес «/ inbox; expand = true» приведет к состоянию маршрутизатора, в котором для двух активированных маршрутов параметр расширения имеет значение true.

Стратегии соответствия

По умолчанию маршрутизатор проверяет, начинается ли URL-адрес со свойства path для маршрута, то есть он проверяет, имеет ли URL-адрес префикс пути. Это неявное значение по умолчанию, но мы можем установить эту стратегию явно следующим образом:

Маршрутизатор поддерживает вторую стратегию сопоставления - полную, которая проверяет, что путь «равен» тому, что осталось в URL-адресе. Это наиболее важно для переадресации. Чтобы понять, почему, давайте посмотрим на этот пример:

Поскольку стратегия сопоставления по умолчанию - префикс, а любой URL-адрес начинается с пустой строки, маршрутизатор всегда будет соответствовать первому маршруту. Даже если мы перейдем к «/ inbox», маршрутизатор применит первое перенаправление. Однако наша цель - сопоставить второй маршрут при переходе к «/ inbox» и перенаправить на «/ inbox» при переходе к «/». Теперь, если мы изменим стратегию сопоставления на «полную», маршрутизатор будет применять перенаправление только при переходе к «/».

Безкомпонентные маршруты

У большинства маршрутов в конфигурации установлены свойства redirectTo или component, но у некоторых нет ни того, ни другого. Например, посмотрите на маршрут «путь:‘: папка ’» в конфигурации ниже.

Мы назвали такие маршруты «бескомпонентными» маршрутами. Их основная цель - использовать сегменты URL, предоставлять некоторые данные своим дочерним элементам и делать это без создания экземпляров каких-либо компонентов.

Параметры, захваченные безкомпонентным маршрутом, будут объединены с их дочерними параметрами. Данные, разрешенные безкомпонентным маршрутом, также будут объединены. В этом примере оба дочерних маршрута будут иметь параметр папки в своих картах параметров.

Этот конкретный пример можно было бы легко переписать следующим образом:

Нам нужно продублировать параметр «: folder», но в целом он работает. Иногда, однако, нет другого хорошего варианта, кроме как использовать безкомпонентный маршрут.

Близкие компоненты, использующие одни и те же данные

Например, полезно разделять параметры между одноуровневыми компонентами.

В следующем примере у нас есть два компонента - MessageListCmp и MessageDetailsCmp - которые мы хотим разместить рядом друг с другом, и для обоих из них требуется параметр идентификатора сообщения. MessageListCmp использует идентификатор для выделения выбранного сообщения, а MessageDetailsCmp использует его для отображения информации о сообщении.

Один из способов смоделировать это - создать поддельный родительский компонент, из которого как MessageListCmp, так и MessageDetailsCmp могут получить параметр id, т.е. мы можем смоделировать это решение со следующей конфигурацией:

При такой конфигурации переход к «/ messages / 11» приведет к следующему дереву компонентов:

У этого решения есть проблема - нам нужно создать фиктивный компонент, который не служит реальной цели. Вот где хорошее решение - безкомпонентные маршруты:

Теперь при переходе к «/ messages / 11» маршрутизатор создаст следующее дерево компонентов:

Составление безкомпонентных маршрутов и маршрутов с пустым путем

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

Позвольте привести пример. Мы узнали, что можем использовать маршруты с пустым путем для создания экземпляров компонентов без использования каких-либо сегментов URL-адреса, и мы можем использовать маршруты без компонентов для использования сегментов URL-адресов без создания экземпляров компонентов. А как насчет их объединения?

Здесь мы определили маршрут, который не использует какие-либо сегменты URL-адреса и не создает никаких компонентов, а используется просто для запуска охранников и выборки данных, которые будут использоваться как MesssagesCmp, так и ContactsCmp. Дублирование их в дочерних элементах невозможно, поскольку и защита, и преобразователь данных могут быть дорогостоящими асинхронными операциями, и мы хотим запустить их только один раз.

Резюме

Мы многому научились! Сначала мы поговорили о том, как роутер выполняет сопоставление. Он проходит по предоставленным маршрутам один за другим, проверяя, начинается ли URL с пути маршрута. Затем мы узнали, что маршрутизатор не имеет понятия о специфике. Он просто просматривает конфигурацию в порядке «сначала в глубину» и останавливается после нахождения пути, соответствующего всему URL-адресу, то есть порядок маршрутов в конфигурации имеет значение. После этого мы поговорили о маршрутах с пустым путем, которые не используют сегменты URL, и о маршрутах без компонентов, которые не создают экземпляры каких-либо компонентов. Мы показали, как мы можем использовать их для обработки сложных случаев использования.

Учить больше

Виктор Савкин - соучредитель Nrwl. Мы помогаем компаниям развиваться, как Google, с 2016 года. Мы предоставляем консультации, инжиниринг и инструменты.

Если вам это понравилось, нажмите 👏 ниже, чтобы другие люди увидели это здесь, на Medium. Подпишитесь на @victorsavkin, чтобы узнать больше о монорепозиториях, Nx, Angular и React.