За всем этим просматривается общая тенденция, которая, по моему мнению, заключается в том, что функционал роутинга в многих популярных фронтэнд фрейморках перегружен. В связи с этим, он становится жестко связанным с другими компонентами, которые могли быть выделены из роутинга (например с навигацией, историей, ссылками и т.п.). Поэтому, неверное, многим знакомо то чувство, когда использование роутинга становится неудобным, а его расширение просто невозможным. По сравнению с гибкими и расширяемыми компонентами, роутинг в популярных фронтэнд фрейморках выглядит на порядок менее удобным и совсем не расширяемым. Особенно это относится первым версиям (до 4-й) роутинга в React.
В этом сообщении я рассмотрю некоторые исторические моменты, которые привели к такому положению дел с роутингом, а также использование библиотеки universal-router, совместно с React.
А нужен ли роутинг?
Технически одностраничное веб-приложения может работать без роутинга. Например, как нет роутинга в десктопном приложении. Все работало бы почти хорошо, если бы одностраничное веб-приложение не оставалось все тем же веб-приложением для браузера. То есть, пользователь может в любую минуту обновить страницу нажатием на клавишу F5 или кликом по пиктограмме «Reload» браузера. Или же пользователь может в любой момент прокрутить историю вперед или назад кликом по пиктограмме «Стрелка влево» и «Стрелка вправо», или нажатием на клавишу «Backspace».
Поэтому, для одностраничного приложения смена компонентов и изменения внутреннего состояния приложения всегда должно сопровождаться изменением url.
Почему роутинг такой?
На функцию роутинга в популярных фреймворках для фронтэнда веб-приложений, по моему мнению, влияет его историческая связь роутингом в классических веб-приложениях (с серверным рендерингом).
Первоначально, url был адресом в сети статического веб-документа, и все было очень просто. Далее началась адаптация архитектуры MVC применительно к вебу: Model 1 и Model 2. Последняя из них имеет в своем составе фронт-контролеер, который впоследствии был еще разделен на две части: роутинг (который выбирает нужный контроллер) и собственно контроллер который работает с моделью и рендерит вью. Как видим, в классическом веб-приложении роутинг определяет действие (контроллер) и, опосредованно (через контроллер), определяет вью которое должно быть отренедрено на сервере.
То есть, десктопная архитектура была в свое время адаптирована для работы с классическим веб-приложением на сервере, а потом вернулась на фронт веб-приложения в виде роутинга, который был утяжелен функциями, которые были необходимы на стороне сервера.
Что предлагает библиотека universal-router?
Библиотека universal-router предлагает отбросить все лишнее и оставить только ту часть, которая может быть использована с любым фреймворком или без него, при рендеринге как на клиенте, так и на стороне веб-сервера (в универсальных/изоморфных веб-приложениях).
Отбросив все напластования времен, universal-router предлагает всего лишь одну четко обозначенную функцию. На основании строки (еще раз подчеркиваю строки а не объекта history, location и т.п.) вызвать асинхронную функцию, которой передать виде фактических параметров разобранную строку url. Вот и все. Как это могло бы выглядеть в React:
import React from 'react'; import UniversalRouter from 'universal-router'; import App from './App'; import Link from './Link'; const routes = { path: '/', async action({next}) { const children = await next(); return ( <App> {children} </App> ); }, children: [ { path: '', async action() { return ( <div>Root route go to <Link href='/test'>Test</Link></div> ); }, }, { path: '/test', async action({next}) { const children = await next(); return ( <App> {children} </App> ); }, children: [ { path: '', async action() { return ( <div>Test route return to <Link href='/'>Root</Link></div> ); }, }, ] }, ], }; export const basename = ''; const router = new UniversalRouter(routes, { baseUrl: basename }); export default router;
Вложеные роуты также поддерживаются. Они определяются в поле children, а получить их можно вызовом асинхронной функции next().
И как же это работает с React?
Определим метод navigate() для history:
import { createBrowserHistory } from 'history' import parse from 'url-parse' import deepEqual from 'deep-equal' const isNode = new Function('try {return this===global;}catch(e){return false;}') //eslint-disable-line let history if (!isNode()) { history = createBrowserHistory() history.navigate = function (path, state) { const parsedPath = parse(path) const location = history.location if (parsedPath.pathname === location.pathname && parsedPath.query === location.search && parsedPath.hash === location.hash && deepEqual(state, location.state)) { return } const args = Array.from(arguments) args.splice(0, 2) return history.push(...[path, state, ...args]) } } else { history = {} history.navigate = function () {} } export default history
Также создадим компонент Link, который будет вызывать навигацию:
import React from 'react'; import {basename} from './router'; import history from './history'; const createOnClickAnchor = (callback) => { return (e) => { e.preventDefault(); history.navigate(e.currentTarget.getAttribute('href')); callback(e); }; }; export default ({href, onClick = () => {}, children, ...rest}) => ( <a href={basename + href} onClick={createOnClickAnchor(onClick)} {...rest} > {children} </a> );
Теперь все готово для рендеринга компонента:
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; import history from './history'; import router from './router'; const render = async (location) => { const element = await router.resolve(location); ReactDOM.render( element, document.getElementById('root'), ); }; render(history.location); history.listen(render); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.unregister();
Код проекта github.com/apapacy/universal-router-tut
Полезные ссылки
1. medium.com/@ippei.tanaka/universal-router-history-react-97ec79464573
ссылка на оригинал статьи https://habr.com/ru/post/484514/
Добавить комментарий