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

Имея это в виду, недавно мне стало любопытно, как разные фреймворки реализуют свою логику маршрутизации. Я обнаружил удивительное разнообразие реализаций, которые довольно много раскрывают способ структурирования фреймворков и стоящие за ними цели. Я исследовал фреймворки, которые я считаю наиболее распространенными и современными фреймворками в JavaScript в настоящее время: Angular 2, Ember, React, Vue, Meteor и Aurelia.

У каждого из этих фреймворков много поклонников. Дальнейшее обсуждение будет полностью сосредоточено только на синтаксисе и структуре маршрутизатора, и его не следует воспринимать как критику или похвалу всего фреймворка в целом. Само собой разумеется, что следующее в значительной степени субъективно. Я не эксперт по всем этим фреймворкам и пытаюсь открыто и честно взглянуть на эти функции на основе их официальной документации и некоторой дополнительной информации. Были предприняты все попытки предоставить точную информацию с благими намерениями, но если функции не обсуждаются правильно, пожалуйста, не стесняйтесь комментировать соответственно, и запись будет исправлена.

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

Я хочу сразу признать, что мой главный опыт работы - это разработчик Ember, и эта статья написана на этой основе. Цель здесь - поделиться своими знаниями по ряду технологий, а не участвовать в бесконечных спорах о специфике фреймворка.

Маршрутизатор

// posts
export const PostRoutes: RouterConfig = [{
    path: '/posts',
    component: PostListComponent,
    children: [
        { path: ':id', component: PostComponent },
        { path: '', component: PostListComponent }
    ]
}];

Синтаксис роутера Angular очень ясен. Это TypeScript, который добавляет немного уникальности, но при этом ясен и удобочитаем. Единственное, что усложняет или подрывает маршрутизатор, - это то, что рекомендуется использовать, по-видимому, отдельные части RouterConfig для разделов приложения, разбросанные по всему приложению. Предварительное отображение всей структуры маршрута на сайте может быть полезным для понимания и обзора. Вопрос о том, следует ли ими управлять централизованно или разделять по интересующим их областям, довольно субъективно и не заслуживает критики. Как и многие другие варианты фреймворка, этот очень открыт для интерпретации.

Простые ссылки

<a routerLink="/about">About</a>
<a routerLink="/posts">Posts</a>

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

Динамические ссылки

<li *ngFor="let post of posts">
    <a [routerLink]="['/posts', post.id]">{{post.title}}</a>
</li>

К сожалению, именно здесь Angular 2 опускается до особого синтаксического супа. Позвольте мне сказать субъективно и полностью, исходя из мнения: это мерзко. Я знаю, что есть причины и смысл, и я знаю, что Angular всегда действовал именно так, но ... ну.

Если говорить более объективно, это фактически не поддерживается по умолчанию. Не все компоненты можно развести таким образом с места в карьер. Они должны быть настроены как ActivatedRoute через конструктор связанного компонента, и к компоненту связывания также необходимо добавить директиву shouty. Почему это не стандартное поведение, остается загадкой, и я искренне обеспокоен тем, что что-то упустил.

У меня такое чувство, что если я ошибаюсь, кто-нибудь даст мне знать ...

Маршрутизатор

FlowRouter.route(‘/’, {
    name: ‘App.home’,
    action() {
        BlazeLayout.render(‘App_body’, {main: ‘app_rootRedirector’  });
    }
});
FlowRouter.route(‘/about’, {
    name: ‘About’,
    action() {
        BlazeLayout.render(‘App_body’, {main: ‘About_page’});
});
});
FlowRouter.route('/posts', {
    name: 'Posts.list',
    action() {
        BlazeLayout.render('App_body', { main: 'Post_list_page' });
    }
});
FlowRouter.route('/posts/:_id', {
    name: 'Posts.show',
    action() {
        BlazeLayout.render('App_body', { main: 'Lists_show_page' });
    }
});

Meteor необычен тем, что в нем нет ни одного роутера. Есть два. Изначально я сосредоточился на Iron Router, потому что, как я читал, предполагал, что он был «по умолчанию» для Meteor. Однако официальные руководства Meteor используют Flow, поэтому я изменил треки. Кажется, идет много споров о том, какой из них использовать. Я должен сказать заранее, что считаю это против Meteor. Это именно то решение, которое я не хочу принимать в рамках.

Я также должен признать, и это весьма субъективно, что мне не нравится структура Meteor по этому поводу. Такое ощущение, что каждый FlowRouter.route () делает слишком много. Больше всего это напоминает мне базовую реализацию маршрутизации во фреймворке Laravel.

Route::get('/posts', function () {
    return Posts::all();
});

Однако такое использование на самом деле предназначено только как наивная иллюстрация или конкретный случай. При фактическом использовании в любой разумной системе вы гораздо чаще будете ссылаться на структуру controller @ method. У Meteor, похоже, нет такого потенциала для улучшения. Обратите внимание, что конкурирующий Iron Router на самом деле не меняет этого. Его структура такая же.

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

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

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

Простые ссылки

<a href="{{pathFor route='About'}}">About</a>
<a href="{{pathFor route='Posts.list'}}">Posts</a>

Хотя использование простого рукописного URL-адреса было бы вполне допустимо, это считается более удобным в обслуживании. К сожалению, показанный выше синтаксис не включен по умолчанию, и его необходимо установить. Это кажется удивительным упущением. Синтаксис Meteor для этого должен быть знаком многим - он использует клавиши пробела, вариант Handlebars.

Динамические ссылки

{{#each list in lists}}
  <a href="{{pathFor ‘Lists.show’ _id=list._id}}">{{list.name}}</a>
{{/each}}

Опять же, это синтаксис в стиле Handlebars, который кому-то понравится, а кому-то не понравится. И снова этот pathFor не является частью установки Meteor по умолчанию. Мне было бы интересно узнать, выполняет ли привязка pathFor нечто большее, чем генерация строки, или действительно облегчает отслеживание ссылки. Но это выходит за рамки данной статьи.

Маршрутизатор

Router.map(function() {
    this.route('about');
    this.route('posts', function() {
        this.route('show', {path: ':post_id'});
    });
});

Маршрутизатор Ember впечатляет своей краткостью. У Ember есть довольно уникальная объектная сущность, которая называется Маршрут. Это просто объект, связанный с любым заданным шаблоном URL, и обрабатывающий хуки жизненного цикла, данные модели и т. Д. Ember имеет чрезвычайно жесткие соглашения об именах, которые обеспечивают соблюдение таких вещей, как имена шаблонов и структура проекта, поэтому в этом случае, например, есть Route который находится в app / routes / about.js. Поскольку этот должен быть файлом, поддерживающим этот URL, указывать его не нужно.

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

Простые ссылки

{{#link-to 'about'}}About{{/link-to}}
{{#link-to 'posts'}}Posts{{/link-to}}

Синтаксис ссылок похож на Meteor, опять же из-за системы шаблонов, производных от Handlebars. Это «помощник по ссылке». Это сильно отличается от страны JSX или шаблонов, включающих компоненты, но некоторые люди предпочитают более отдельный строгий шаблон MVC.

Драконовские условности, твердые мнения и принудительное разделение интересов Ember - это либо ее самое большое преимущество, либо серьезный недостаток.

Динамические ссылки

{{#each model as post}}
    <li>{{#link-to 'posts.show' post}}posts{{/link-to}}</li>
{{/each}}

Опять же, очень субъективно, чисто это или нет (мне это нравится), но есть более объективная выгода. В этом случае мы передали объект сообщения помощнику по ссылке. Поскольку у Ember теперь есть модель для этого маршрута, ему не нужно запускать обработчик модели, когда он выполняет этот переход. Это означает, что первоначальный вызов XHR для получения коллекции сообщений будет первым и последним, который необходимо выполнить. Хорошее поведение по умолчанию, которое, как мне кажется, не поддерживается другими фреймворками без явной настройки и управления состоянием.

Маршрутизатор

render((
<Router history={hashHistory}>
    <Route path="/" component={App}>
        <IndexRoute component={Home} />
        <Route path="about" component={About} />
        <Route path="posts" component={PostList}>
            <Route path=":id" component={PostShow}>
        </Route>
    </Route>
</Router>
), document.body)

Я никак не могу сказать это объективно. Я считаю, что JSX ужасен. Я думаю, это мерзко. Это нарушает принципы хорошей разработки программного обеспечения, которые я использовал на протяжении всей своей карьеры, и IMO выглядит просто уродливо. Все в этом заставляет меня съеживаться. React - фантастическая библиотека, но эта чушь XML странная. Существует альтернативный синтаксис, который позже будет привязан к JSX, но я хотел придерживаться идиоматических стандартов, и вот он. Да помилует Бог его душу.

Для меня также загадка, почему то, что по сути является внутренней конфигурацией, должно «отображаться». И еще более загадочно, почему конфиг должен отображаться на указанном элементе DOM.

Простые ссылки

<ul>
    <li><Link to="/about">About</Link></li>
    <li><Link to"/posts">Posts</Link></li>
</ul>

Нетипично, но не однозначно, React заменяет весь тег привязки на собственный. Опять же, особо нечего об этом сказать.

Динамические ссылки

{this.state.posts.map(user => (
<li key={post.id}>
    <Link to={`/posts/${post.id}`}>{post.title}</Link>
</li>
))}

Как и в случае с Angular 2, ясность этого немного теряется из-за немного эзотерического синтаксиса. Хотя, честно говоря, это скорее ES6, чем React. Это надежное использование шаблонных литералов ES6. С первого взгляда достаточно очевидно, для чего он нужен. Возможно, это не для всех, но он ясный и современный.

Маршрутизатор

router.map({
    '/': {component: HomePage},
    '/about': {name: 'about', component: About},
    '/posts': {
        name: 'postList',
        component: PostList,
        subRoutes: {
            '/:postId': {name: 'post', component: ShowPost}
        }
    }
})

Маршрутизатор Vue чистый и легко читаемый. Он эффективно связывает маршрут с объектом, который содержит все необходимое, в частности, компонент. Как и Angular 2, он явно вкладывает подмаршруты, что является определенным предпочтением.

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

Простые ссылки

<a v-link="{ path: '/about' }">About</a>
<a v-link="{ path: '/posts' }">Posts</a>

Опять же, в отличие от подхода Angular, Vue использует настраиваемый атрибут для управления ссылкой маршрута.

Динамические ссылки

<li v-for="post in posts">
    <a v-link="{ name: 'post', params: { postId: post.id }}”>{{post.title}}</a>
</li>

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

Маршрутизатор

config.map([
    { route: ['', 'home'], name: 'home', moduleId: 'home/index' },
    { route: 'about', name: 'about', moduleId: 'about/index', nav: true },
    { route: 'posts', name: 'postList', moduleId: 'post/index', nav: true },
    { route: 'posts/:postId', name: 'postShow', moduleId: 'post/show' },
]);

Это довольно прилично. Неочевидны только две вещи. Прежде всего, что такое moduleId? Ни один из примеров или документации на самом деле не объясняет этого. В Aurelia Land это считается само собой разумеющимся и используется без объяснения причин. При значительном объеме поиска выясняется, что речь идет именно о сочетании представления и модели представления.

Это единственная структура, которая не поддерживает явное вложение. Вы можете видеть, что маршруты postList и postShow находятся на одном уровне. Объективно это не имеет большого значения, но может устранить некоторую оптимизацию, а также сделать структуру немного менее понятной.

Следует отметить nav: true, который я оставил на месте, несмотря на то, что он вышел за рамки. Этот ключ сообщает Аурелии, какими будут элементы навигации верхнего уровня, и облегчает эту возможность:

<li repeat.for="row of router.navigation">
    <a href.bind="row.href">${row.title}</a>
</li>

Неплохой вариант стандартного поведения из коробки.

Простые ссылки

<a route-href="route: about">About</a>
<a route-href="route: posts">Posts</a>

Маршрут Аурелии хороший и чистый. В нем прямо говорится о некоторых вещах, которые должны сделать пользователи Ember. Но процесс связывания выглядит странно запутанным и неясным. Стоит ли использовать href.bind? Или route-href? Последнее, как правило, предпочтительнее, первое используется для внутренних ссылок.

Динамические ссылки

<li repeat.for="post of posts">
    <a route-href="route: post, params.bind: { postId: post.id }”>${post.title}</a>
</li>

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

Заключение

Кажется, есть три основных шаблона с точки зрения синтаксиса маршрутизаторов. Реализация по умолчанию заключается в отображении строки (или шаблона строки) на данный компонент. Этому шаблону следуют Angular 2, React, Vue и Aurelia (через moduleId).

Двумя исключениями являются Meteor и Ember. Кажется, что идиоматический метод Meteor проходит через закрытие, которое затем явно устанавливает шаблон и данные. Это обсуждается более подробно в разделе, посвященном метеору. Ember - еще один радикальный вариант. Его структура впечатляюще лаконична из-за того, что он опирается на жесткие условности. Но разработчики, менее знакомые с этими соглашениями, будут бороться с отсутствием явных деталей - они могут видеть, что маршрут «about» поддерживается, но не как.

Глядя на эти примеры, сразу становится очевидным, какое влияние Angular оказал на ландшафт. Помимо очевидности Angular 2, Aurelia и Vue демонстрируют довольно явные признаки своего влияния, особенно в синтаксисе стиля ‹li repeat.for›. Ember и Meteor внешне похожи, но это только потому, что в них используются почти идентичные движки шаблонов. React, как всегда, идет своим путем.

Подводя итог: Ember, как всегда, требует знаний о своей структуре и условных обозначениях. React - это неплохо, но JSX - это вызов Богу и Человеку. Aurelia выглядит хорошо и хорошо работает, но ее документации очень не хватает, и я удивлен, что в ней нет маршрутов. Структура маршрутов Angular 2 превосходна, но ее шаблоны - это хорошо задокументированный мусорный огонь. Vue был выдающимся, четким и выразительным роутером с рядом солидных функций. Его документация превосходна, а шаблоны, хотя и довольно «угловатые», хорошо реализованы и функциональны. Meteor (к моему большому удивлению) выделялся по противоположным причинам. С двумя конкурирующими маршрутизаторами, потенциально способными работать в двух разных контекстах (сервер и клиент), сомнительной документацией, минимальным количеством примеров кода и структурой, которая кажется не очень удобной в обслуживании. Даже использование шаблонов рулей омрачено некрасивыми условностями.