В рамках маршрутов в React можно определять дочерние маршруты. Такие подмаршруты будут отсчитываться от главного маршрута. Но для построения подобной системы есть ряд подходов. Рассмотрим их.
Для применения подмаршрутов возьмем из прошлой темы веб-страницу index.html и изменим ее следующим образом:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Маршруты в React</title> </head> <body> <div id="app"></div> <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> <script src="https://unpkg.com/history@5/umd/history.production.min.js" crossorigin></script> <script src="https://unpkg.com/react-router@6.3.0/umd/react-router.production.min.js" crossorigin></script> <script src="https://unpkg.com/react-router-dom@6.3.0/umd/react-router-dom.production.min.js" crossorigin></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <script type="text/babel"> const Router = ReactRouterDOM.BrowserRouter; const Route = ReactRouterDOM.Route; const Routes = ReactRouterDOM.Routes; function Phone(){ return <h3>Смартфоны</h3>; } function Tablet(){ return <h3>Планшеты</h3>; } function Products(){ return <div> <h2>Товары</h2> <Routes> <Route path="/phones" element={<Phone />} /> <Route path="/tablets" element={<Tablet />} /> </Routes> </div>; } ReactDOM.createRoot( document.getElementById("app") ) .render( <Router> <div> <Routes> <Route path="/" element={<h2>Главная</h2>} /> <Route path="/products/*" element={<Products />} /> <Route path="*" element={<h2>Ресурс не найден</h2>} /> </Routes> </div> </Router> ); </script> </body> </html>
Для обработки запроса "/products" здесь определен маршрут, который обрабатывается компонентом Products:
<Route path="/products/*" element={<Products />} />
Обратите внимание на шаблон пути: path="/products/*"
. Символ * указывает, что компонент Products будет обрабатывать маршруты, которые начинаются
"/products/", но после слеша также могут идти и другие символы.
Но сам этот компонент имеет вложенные маршруты:
function Products(){ return <div> <h2>Товары</h2> <Routes> <Route path="/phones" element={<Phone />} /> <Route path="/tablets" element={<Tablet />} /> </Routes> </div>; }
Вложенные маршруты отсчитываются фактически от главного маршрута "/products". То есть маршрут
<Route path="/phones" element={<Phone />} />
будет обрабатывать запросы по пути "/phones", который добавляется к пути главного компонента - "/products", то есть в итоге по пути "/products/phones". Аналогичным образом запросы по пути "/products/tablets" будут обрабатываться компонентом Tablet.
Запустим приложение и пройдемся по разным адресам:
И что также можно заметить, то в обоих случаях выводится заголовок "Товары", так как он определен на главном компоненте Products. Остальное содержимое будет отличаться в зависимости от выбранного для обработки запроса компонента.
Аналогичный пример с использованием классов:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Маршруты в React</title> </head> <body> <div id="app"></div> <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> <script src="https://unpkg.com/history@5/umd/history.production.min.js" crossorigin></script> <script src="https://unpkg.com/react-router@6.3.0/umd/react-router.production.min.js" crossorigin></script> <script src="https://unpkg.com/react-router-dom@6.3.0/umd/react-router-dom.production.min.js" crossorigin></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <script type="text/babel"> const Router = ReactRouterDOM.BrowserRouter; const Route = ReactRouterDOM.Route; const Routes = ReactRouterDOM.Routes; class Phone extends React.Component{ render(){ return <h3>Смартфоны</h3>; } } class Tablet extends React.Component{ render(){ return <h3>Планшеты</h3>; } } class Products extends React.Component{ render(){ return <div> <h2>Товары</h2> <Routes> <Route path="/phones" element={<Phone />} /> <Route path="/tablets" element={<Tablet />} /> </Routes> </div>; } } ReactDOM.createRoot( document.getElementById("app") ) .render( <Router> <div> <Routes> <Route path="/" element={<h2>Главная</h2>} /> <Route path="/products/*" element={<Products />} /> <Route path="*" element={<h2>Ресурс не найден</h2>} /> </Routes> </div> </Router> ); </script> </body> </html>
Однако данный подход имеет как минимум один недостаток - при обращении к любым адресам, которые начинаются с "products", запросы будет обрабатывать компонент Products. Но, к примеру, мы хотим, чтобы он обрабатывал свой основной маршрут и запросы по дочерним маршутам. Поэтому рассмотрим другой подход.
При другом подходе маршруты определяются вне компонента, а их содержимое вставляется в главный компонент с помощью встроенного компонента Outlet. Так, изменим предыдущий пример следующим образом:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Маршруты в React</title> </head> <body> <div id="app"></div> <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> <script src="https://unpkg.com/history@5/umd/history.production.min.js" crossorigin></script> <script src="https://unpkg.com/react-router@6.3.0/umd/react-router.production.min.js" crossorigin></script> <script src="https://unpkg.com/react-router-dom@6.3.0/umd/react-router-dom.production.min.js" crossorigin></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <script type="text/babel"> const Router = ReactRouterDOM.BrowserRouter; const Route = ReactRouterDOM.Route; const Routes = ReactRouterDOM.Routes; const Outlet = ReactRouterDOM.Outlet; function Phone(){ return <h3>Смартфоны</h3>;} function Tablet(){ return <h3>Планшеты</h3>; } function Products(){ return <div> <h2>Товары</h2> <Outlet /> </div>; } ReactDOM.createRoot( document.getElementById("app") ) .render( <Router> <div> <Routes> <Route path="/" element={<h2>Главная</h2>} /> <Route path="/products" element={<Products />}> <Route path="phones" element={<Phone />} /> <Route path="tablets" element={<Tablet />} /> </Route> <Route path="*" element={<h2>Ресурс не найден</h2>} /> </Routes> </div> </Router> ); </script> </body> </html>
Здесь надо отметить два момента. Прежле всего в коде компонента Products применяется компонент Outlet:
function Products(){ return <div> <h2>Товары</h2> <Outlet /> </div>; }
Здесь вместо элемента <Outlet />
будет вставляться содержимое компонентов, которые обрабатывают дочерние маршруты.
Во-вторых, дочерние маршруты определены внутри основного маршрута:
<Route path="/products" element={<Products />}> <Route path="phones" element={<Phone />} /> <Route path="tablets" element={<Tablet />} /> </Route>
Стоит отметить, что пути в дочерних маршрутах не должны начинаться со слеша. И в итоге дочерние маршруты также будут отсчитываються от главного маршрута "/products" и сопвадать с запросами "/products/phones" и "/products/tablets". Запрос по основному маршруту "/products" будет обрабатываться только компонентом Products. Все остальные запросы, которые не совпадают с основным и подмаршрутами, например, "/products/abc", будет обрабатываться самым последним маршрутом.
Аналогичный пример с использованием компонентов-классов:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Маршруты в React</title> </head> <body> <div id="app"></div> <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> <script src="https://unpkg.com/history@5/umd/history.production.min.js" crossorigin></script> <script src="https://unpkg.com/react-router@6.3.0/umd/react-router.production.min.js" crossorigin></script> <script src="https://unpkg.com/react-router-dom@6.3.0/umd/react-router-dom.production.min.js" crossorigin></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <script type="text/babel"> const Router = ReactRouterDOM.BrowserRouter; const Route = ReactRouterDOM.Route; const Routes = ReactRouterDOM.Routes; const Outlet = ReactRouterDOM.Outlet; class Phone extends React.Component{ render(){ return <h3>Смартфоны</h3>; } } class Tablet extends React.Component{ render(){ return <h3>Планшеты</h3>; } } class Products extends React.Component{ render(){ return <div> <h2>Товары</h2> <Outlet /> </div>; } } ReactDOM.createRoot( document.getElementById("app") ) .render( <Router> <div> <Routes> <Route path="/" element={<h2>Главная</h2>} /> <Route path="/products" element={<Products />}> <Route path="phones" element={<Phone />} /> <Route path="tablets" element={<Tablet />} /> </Route> <Route path="*" element={<h2>Ресурс не найден</h2>} /> </Routes> </div> </Router> ); </script> </body> </html>
Нередко возникает необходимость среди дочерних маршрутов выделить основной подмаршрут. Для установки такого подмаршрута применяется атрибут index. Например, изменим возьмем пример выше и изменим определение маршрутов:
<Router> <div> <Routes> <Route path="/" element={<h2>Главная</h2>} /> <Route path="/products" element={<Products />}> <Route index element={<h3>Каталог товаров</h3>} /> <Route path="phones" element={<Phone />} /> <Route path="tablets" element={<Tablet />} /> </Route> <Route path="*" element={<h2>Ресурс не найден</h2>} /> </Routes> </div> </Router>,
В данном случае для маршрута с путем "/products" определен основной подмаршрут
<Route index element={<h3>Каталог товаров</h3>} />
Для простоты здесь явным образом не создается компонент для обработки этого маршрута, но естестественно можно также создать отдельный компонент.
В любом случае содержимое этого компонента будет также вставляться в компонент Products на место элемента Outlet
: