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

Angular, Ember и React имеют разные решения для маршрутизации. Но у них есть одна общая черта: они сложны и требуют времени для изучения.

Возьмем, к примеру, Ember. Вот типичный файл маршрутизатора:

Router.map(function() {
  this.route('about', { path: '/about' });
  this.route('posts', function() {
    this.route('new');
    this.route('favorites');
  });
});

Первый вопрос, который приходит на ум, — почему на некоторых маршрутах есть apath, а на других — нет? Где обработчики этих маршрутов? Мы имеем в виду this внутри функций обратного вызова, нужно ли их привязывать?

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

React Router, с другой стороны, творит меньше магии и сохраняет ясность. Но он по-прежнему страдает от того же раздувания API и проблем с кривой обучения.

<Router>
  <Route path="/" component={App}>
    <IndexRoute component={AppIndex} />
    <Route path="about" component={About} />

    <Route path="posts" component={Posts}>
      <Route path="new" component={NewPost} />
      <Route path="favorite" component={FavoritePosts} />
    </Route>
  </Route>
</Router>

Похожие вопросы: какие еще реквизиты поддерживает <Route>? Что такое индексный маршрут и чем App отличается от AppIndex? И т.д..

Сделаем шаг назад

Какова основная обязанность маршрутизатора? В мире клиентской части маршрутизатор несет единственную ответственность за сопоставление путей URL-адресов с содержимым пользовательского интерфейса. Это так просто. Точно так же на стороне сервера маршрутизатор сопоставляет HTTP-запросы с HTTP-ответами.

Поскольку маршрутизация — это просто сопоставление, почему бы не использовать обычные объекты JavaScript?

// routes.js
{
  'about': <About />,
  'posts': {
    'new': <NewPost />,
    'favorites': <FavoritePosts />,
  }
}

Что ж, выглядит многообещающе. Но ждать. Это все статические компоненты. Что делать, если мне нужна динамическая маршрутизация? Что, если я хочу визуализировать разные вещи на основе какой-то переменной? К счастью, функции в JS являются гражданами первого класса:

// routes.js
{
  'about': <About />,
  'posts': {
    'favorites': () => isLoggedIn() ? <Favorites /> : <Login />,
    '{id}': (route) => <Post id={route.params.id} />,
  }
}

Хорошо, но что, если мне нужно получить некоторые данные перед визуализацией компонента? Что делать, если мне нужно лениво загрузить компонент? Обещания приходят на помощь!

// routes.js
{
  'about': () => lazyLoad('About').then(About => <About />),
  'posts': {
    'favorites':
      () => fetchFavs().then(favs => <Favorites favs={favs} />),
  }
}

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

// posts/routes.js
export default {
  'favorites': ...,
  '{id}': ...,
};
// profile/routes.js
export default {
  'view': <ProfileView />,
  'settings': <ProfileSettings />,
};
// routes.js
import About from './about';
import PostsRoutes from './posts/routes';
import ProfileRoutes from './profile/routes';
export default {
  'about': <About />,
  'posts': PostsRoutes,
  'profile': ProfileRoutes,
};

Спасибо за чтение! Буду рад услышать ваши мысли :)