Пошаговое руководство по рендерингу нескольких макетов в React с использованием новой версии react-router-dom 6.
Многое изменилось в новом пакете react-router-dom, такие как:
component
заменен на element
exact
больше не поддерживаетсяSwitch
заменен на Routes
useHistory()
заменен на useNavigate()
Redirect
заменен на Navigate
Хорошо, давайте посмотрим, как все будет.
Откройте терминал и перейдите в папку, где вы хотите создать свой проект:
$ npx create-react-app multiple-layouts
В случае, если вы хотите создать свое приложение с использованием TypeScript:
$ npx create-react-app multiple-layouts --template typescript
Теперь давайте установим пакет react-router-dom
, а также мы будем использовать пакет lodash
в нашем коде.
$ npm install react-router-dom lodash
// Or
$ yarn add react-router-dom lodash
К моменту написания этой статьи точная версия составляет 6.10.0.
Давайте создадим несколько страниц, чтобы начать настраивать нашу логику работы с роутингом.
Создайте новую папку /pages внутри папки /src и создайте 4 страницы:
src/pages/Login/index.jsx
const Login = () => {
return (
<div>Login</div>
)
}
export default Login;
src/pages/Home/index.jsx
const Home = () => {
return (
<div>Home</div>
)
}
export default Home;
src/pages/ListUsers/index.jsx
const CreateUser = () => {
return (
<div>CreateUser</div>
)
}
export default CreateUser;
src/pages/CreateUser/index.jsx
const ListUsers = () => {
return (
<div>ListUsers</div>
)
}
export default ListUsers;
Давайте создадим наши макеты, учитывая, что у нас всего 2 варианта макета:
Внутри папки /src создайте папку /layouts, которая будет содержать оба макета.
AnonymousLayout:
const AnonymousLayout = () => {
return (
<div>AnonymousLayout</div>
)
}
export default AnonymousLayout;
MainLayout:
const MainLayout = () => {
return (
<div>MainLayout</div>
)
}
export default MainLayout;
Отлично! Теперь давайте перейдем к созданию наших маршрутов.
Как и в предыдущих примерах, внутри папки /src создайте новую папку с именем /routes, которая будет содержать следующие файлы:
/routes/index.js
— это список страниц и макетов./routes/ProtectedRoute/index.jsx
— это компонент, который будет защищать наши маршруты и предотвращать доступ неавторизованных пользователей к страницам./routes/generate-routes.jsx
— в этом файле мы будем перебирать наши маршруты и генерировать маршруты и макеты.Прежде всего, давайте создадим наш массив маршрутов.
Примечание: Массив маршрутов можно использовать также для отображения элементов навигации в заголовке или боковой панели. Поэтому мы рассматриваем наличие ссылок навигации в боковой панели вместе с подэлементами или подменю.
/routes/index.js
// Layouts
import AnonymousLayout from "../layouts/AnonymousLayout";
import MainLayout from "../layouts/MainLayout";
// Pages
import Login from "../pages/Login";
import Home from "../pages/Home";
import CreateUser from "../pages/CreateUser";
import ListUsers from "../pages/ListUsers";
export const routes = [
{
layout: AnonymousLayout,
routes: [
{
name: 'login',
title: 'Login page',
component: Login,
path: '/login',
isPublic: true,
}
]
},
{
layout: MainLayout,
routes: [
{
name: 'home',
title: 'Home page',
component: Home,
path: '/home'
},
{
name: 'users',
title: 'Users',
hasSiderLink: true,
routes: [
{
name: 'list-users',
title: 'List of users',
hasSiderLink: true,
component: ListUsers,
path: '/users'
},
{
name: 'create-user',
title: 'Add user',
hasSiderLink: true,
component: CreateUser,
path: '/users/new'
}
]
}
]
}
];
Давайте более детально рассмотрим каждое свойство и посмотрим, как оно может быть полезным:
Макеты:
Маршруты макета:
/routes/ProtectedRoute/index.jsx
import React from 'react';
import { Navigate, Outlet } from 'react-router-dom';
const ProtectedRoute = ({ isPublic, isAuthorized }) => {
return (isPublic || isAuthorized) ? <Outlet /> : <Navigate to='/login' />
}
export default ProtectedRoute;
В приведенном выше коде мы создали компонент ProtectedRoute
для защиты страниц, требующих входа в систему, от неавторизованных пользователей.
Учитывая, что мы авторизованы, компонент будет принимать два свойства:
Если isPublic
или isAuthorized
равно true
, компонент вернет компонент Outlet
.
Компонент <Outlet> должен использоваться в родительских элементах маршрута для отображения дочерних элементов маршрута - документация react-router.
Остальное объяснение будет дано позже... Читайте полную документацию о компоненте <Outlet> здесь.
/routes/generate-routes.jsx
Итак, давайте импортируем:
Примечание: Вы можете назвать ReactRoutes
, как угодно, в этом примере функция generateFlattenRoutes
вернет компонент с именем Routes
.
import flattenDeep from 'lodash/flattenDeep';
import React from 'react';
import { Route, Routes as ReactRoutes } from 'react-router-dom';
import ProtectedRoute from './ProtectedRoute';
Затем давайте создадим функцию, которая берет наши маршруты и выравнивает их на одном уровне. Трудно понять? Нет проблем, давайте рассмотрим пример ниже:
// The function will take this array.
[2, 4, [5, 41, [100, 200], 500], 10, [50, 30], 30];
// And take all values out to the same level.
[2, 4, 5, 41, 100, 200, 500, 10, 50, 30, 30]
// There will be no nested arrays at all.
Я надеюсь, что приведенный выше пример объяснил суть.
Продолжим в нашей функции generateFlattenRoutes:
import flattenDeep from 'lodash/flattenDeep';
import React from 'react';
import { Route, Routes as ReactRoutes } from 'react-router-dom';
import ProtectedRoute from './ProtectedRoute';
const generateFlattenRoutes = (routes) => {
if (!routes) return [];
return flattenDeep(routes.map(({ routes: subRoutes, ...rest }) => [rest, generateFlattenRoutes(subRoutes)]));
}
Это рекурсивная функция, которая вызывает саму себя каждый раз, когда у маршрута есть вложенный массив маршрутов. Если функция получает неопределенный параметр, она вернет пустой массив, чтобы предотвратить сбой приложения. Как мы знаем, свойство routes является необязательным.
Затем мы создаем основную функцию, которая будет генерировать наши маршруты:
import flattenDeep from 'lodash/flattenDeep';
import React from 'react';
import { Route, Routes as ReactRoutes } from 'react-router-dom';
import ProtectedRoute from './ProtectedRoute';
const generateFlattenRoutes = (routes) => {
if (!routes) return [];
return flattenDeep(routes.map(({ routes: subRoutes, ...rest }) => [rest, generateFlattenRoutes(subRoutes)]));
}
export const renderRoutes = (mainRoutes) => {
const Routes = ({ isAuthorized }) => {
// code here
}
return Routes;
}
Функция выше принимает список макетов в качестве параметра и возвращает компонент с именем Routes
, который принимает единственное свойство с именем isAuthorized
, которое будет передано позже в качестве свойства компоненту ProtectedRoute
.
Функция renderRoutes
вернет компонент Routes
, когда закончит генерацию маршрутов.
Компонент Routes
также вернет список отображенных маршрутов.
import flattenDeep from 'lodash/flattenDeep';
import React from 'react';
import { Route, Routes as ReactRoutes } from 'react-router-dom';
import ProtectedRoute from './ProtectedRoute';
const generateFlattenRoutes = (routes) => {
if (!routes) return [];
return flattenDeep(routes.map(({ routes: subRoutes, ...rest }) => [rest, generateFlattenRoutes(subRoutes)]));
}
export const renderRoutes = (mainRoutes) => {
const Routes = ({ isAuthorized }) => {
const layouts = mainRoutes.map(({ layout: Layout, routes }, index) => {
const subRoutes = generateFlattenRoutes(routes);
return (
<Route key={index} element={<Layout />}>
<Route element={<ProtectedRoute isAuthorized={isAuthorized} />}>
{subRoutes.map(({ component: Component, path, name }) => {
return (
Component && path && (<Route key={name} element={<Component />} path={path} />)
)
})}
</Route>
</Route>
)
});
return <ReactRoutes>{layouts}</ReactRoutes>;
}
return Routes;
}
Давайте посмотрим, что делает этот кусок кода:
layouts
, которая будет содержать результат отображения параметра mainRoutes
.Layout
, чтобы использовать его позже в качестве компонента React. Также мы учитываем индекс элемента в нашей функции обратного вызова.subRoutes
будет содержать наши плоские маршруты, вызывая функцию generateFlattenRoutes
и передавая извлеченные маршруты в качестве аргумента.
const subRoutes = generateFlattenRoutes(routes);
Теперь, в результате... мы возвращаем элемент HTML:
return (
<Route key={index} element={<Layout />}>
<Route element={<ProtectedRoute isAuthorized={isAuthorized} />}>
{subRoutes.map(({ component: Component, path, name }) => {
return (
Component && path && (<Route key={name} element={<Component />} path={path} />)
)
})}
</Route>
</Route>
)
Элемент Route
- это компонент, импортированный из react-router-dom
. Свойство key
примет индекс итерации отображения, а элемент (компонент в v.5 react-router-dom) примет <Layout />. Этот Route будет отображаться как <Layout />.
Дочерний элемент для этого маршрута также является элементом Route
, но он будет представлять компонент ProtectedRoute
, а свойство элемента примет компонент <ProtectedRoute /> со своими свойствами.
Внутри компонента ProtectedRoute
мы будем генерировать дочерние элементы, используя v.5 react-router-dom
, за исключением того, что свойство компонента теперь называется element, как упоминалось ранее.
Константа subRoutes
- это наши плоские маршруты, поэтому мы вызываем метод map, чтобы сгенерировать маршруты, которые будут отображаться в качестве дочерних элементов в компоненте ProtectedRoute
.
Теперь вернемся к компоненту <Outlet>, возвращаемому в компоненте ProtectedRoute
. Он сообщит react-router-dom, где отображать дочерние элементы.
Мы сделали то же самое с компонентом Layout
.
return (
<Route key={index} element={<Layout />}>
{/* ... */}
</Route>
)
В результате мы должны обновить оба макета, чтобы они возвращали <Outlet>, указывающий, где рендерить дочерние элементы... как показано ниже:
AnonymousLayout
import React from 'react';
import { Outlet } from 'react-router-dom';
const AnonymousLayout = () => {
return (
<Outlet />
)
}
export default AnonymousLayout;
MainLayout
import React from 'react';
import { Outlet } from 'react-router-dom';
const MainLayout = () => {
return (
<Outlet />
)
}
export default MainLayout;
Теперь, когда наш генератор маршрутов готов, вернемся к файлу /routes/index.js
и вызовем вышеуказанную функцию, экспортируя результат следующим образом:
// Layouts
import AnonymousLayout from "../layouts/AnonymousLayout";
import MainLayout from "../layouts/MainLayout";
// Pages
import Login from "../pages/Login";
import Home from "../pages/Home";
import CreateUser from "../pages/CreateUser";
import ListUsers from "../pages/ListUsers";
// Don't mess with this code
export const routes = [
{...},
{...}
]
// Just add this line
export const Routes = renderRoutes(routes);
Теперь, когда мы подготовили нашу систему маршрутизации, начинается последний шаг.
В файле index.jsx
оберните компонент App элементом BrowserRouter
, импортированным из react-router-dom
:
import React from 'react';
import ReactDOM from 'react-dom/client';;
import { BrowserRouter } from 'react-router-dom';
import App from './App';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
Затем, переходя к файлу App.jsx
, импортируйте Routes
из ./routes
следующим образом:
import React, { useEffect } from 'react';
import { Routes } from './routes';
const App = () => {
return (
<Routes isAuthorized={true} />
);
}
export default App;
Теперь мы увидели, как работать с несколькими макетами в 6-й версии react-router-dom. Кроме того, мы увидели, как создавать динамические маршруты вместо того, чтобы иметь один файл со всеми маршрутами, добавленными по одному.
Хуки — это функции в React, которые позволяют Вам использовать состояние и другие функции React без написания классов. В этой статье мы будем рассматривать такие хуки, как useToggle, useFirestoreQuery, useMemoCompare, useAsync, useRequireAuth, useRouter, useAuth, useEventListener, useWhyDidYouUpdate, useDarkMode.
Для тех, кто только начинает изучать React, сталкивается с проблемами организации роутов в приложении, т.к. в интернете много материалов по работе react-router более ранних версий. В данной статье будут описаны основная разница и новшества работы с react-router v6.
Пошаговое руководство по рендерингу нескольких макетов в React с использованием новой версии react-router-dom 6.