меню

Рендеринг нескольких макетов в React с помощью react-router-dom v6.

Пошаговое руководство по рендерингу нескольких макетов в React с использованием новой версии react-router-dom 6.

Многое изменилось в новом пакете react-router-dom, такие как:

  • component заменен на element
  • свойство exact больше не поддерживается
  • Switch заменен на Routes
  • useHistory() заменен на useNavigate()
  • Redirect заменен на Navigate

Хорошо, давайте посмотрим, как все будет.

1 - Создать новое приложение.

Откройте терминал и перейдите в папку, где вы хотите создать свой проект:

  
$ npx create-react-app multiple-layouts
 

В случае, если вы хотите создать свое приложение с использованием TypeScript:

  
$ npx create-react-app multiple-layouts --template typescript
 

2 - Установите react-router-dom и lodash.

Теперь давайте установим пакет react-router-dom, а также мы будем использовать пакет lodash в нашем коде.

  
$ npm install react-router-dom lodash
// Or
$ yarn add react-router-dom lodash
  

К моменту написания этой статьи точная версия составляет 6.10.0.

3 - Создание страниц

Давайте создадим несколько страниц, чтобы начать настраивать нашу логику работы с роутингом.

Создайте новую папку /pages внутри папки /src и создайте 4 страницы:

  1. src/pages/Login/index.jsx
         
    const Login = () => {
      return (
        <div>Login</div>
      )
    }
    
    export default Login;
          
       
  2. src/pages/Home/index.jsx
         
    const Home = () => {
      return (
        <div>Home</div>
      )
    }
    
    export default Home;
         
       
  3. src/pages/ListUsers/index.jsx
         
    const CreateUser = () => {
      return (
        <div>CreateUser</div>
      )
    }
    
    export default CreateUser;
         
       
  4. src/pages/CreateUser/index.jsx
         
    const ListUsers = () => {
      return (
        <div>ListUsers</div>
      )
    }
    
    export default ListUsers;
          
       

4 - Создание макетов

Давайте создадим наши макеты, учитывая, что у нас всего 2 варианта макета:

  • AnonymousLayout — используется, когда пользователи не вошли в приложение.
  • MainLayout — используется, когда пользователи вошли в приложение.

Внутри папки /src создайте папку /layouts, которая будет содержать оба макета.

AnonymousLayout:

 
const AnonymousLayout = () => {
  return (
    <div>AnonymousLayout</div>
  )
}

export default AnonymousLayout;
  

MainLayout:

 
const MainLayout = () => {
  return (
    <div>MainLayout</div>
  )
}

export default MainLayout;
 

5 - Создание файлов маршрутов

Отлично! Теперь давайте перейдем к созданию наших маршрутов.

Как и в предыдущих примерах, внутри папки /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'
          }
        ]
      }
    ]
  }
];
  

Давайте более детально рассмотрим каждое свойство и посмотрим, как оно может быть полезным:

Макеты:

  • layout: Целевой макет, который будет оборачивать целевую страницу. [Обязательно]
  • routes: Список маршрутов, которые будут отображаться внутри макета. [Обязательно]

Маршруты макета:

  • name: Название маршрута, которое должно быть уникальным, так как оно будет использоваться в качестве ключа при отображении маршрутов. [Обязательно]
  • title: Текст, который будет отображаться в качестве заголовка вкладки браузера и метки навигации. [Обязательно]
  • hasSiderLink: Булевое свойство, указывающее, должен ли целевой маршрут отображаться в качестве ссылки навигации боковой панели или нет. Когда установлено значение true, маршрут будет отображаться внутри боковой панели. [Необязательно]
  • component: Компонент страницы, который будет отображаться внутри макета, когда будут сопоставлены пути. [Необязательно]
  • path: Связанный путь для компонента страницы. [Необязательно]
  • isPublic: Булевое свойство, указывающее, является ли страница общедоступной или требует входа в систему. Когда установлено значение true, страница будет доступна в анонимном режиме. [Необязательно]
  • routes: Список подмаршрутов для конкретного маршрута. При отображении подмаршрутов в виде выпадающих ссылок навигации, у родительского маршрута не должно быть пути или компонента. [Необязательно]

/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: логическое свойство, которое указывает, имеет ли пользователь действительный JWT или нет.

Если isPublic или isAuthorized равно true, компонент вернет компонент Outlet.

Компонент <Outlet> должен использоваться в родительских элементах маршрута для отображения дочерних элементов маршрута - документация react-router.

Остальное объяснение будет дано позже... Читайте полную документацию о компоненте <Outlet> здесь.

/routes/generate-routes.jsx

Итак, давайте импортируем:

  • Route, Routes как ReactRoutes из react-router-dom.
  • ProtectedRoute из ProtectedRoute.
  • flattenDeep из lodash/flattenDeep.

Примечание: Вы можете назвать 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;
}
  

Давайте посмотрим, что делает этот кусок кода:

  1. Создается константа с именем layouts, которая будет содержать результат отображения параметра mainRoutes.
  2. В обратном вызове функции отображения мы извлекаем макет и его список маршрутов. Конечно, мы переименовали макет в Layout, чтобы использовать его позже в качестве компонента React. Также мы учитываем индекс элемента в нашей функции обратного вызова.
  3. Внутри функции обратного вызова константа 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>
)
  
  1. Элемент Route - это компонент, импортированный из react-router-dom. Свойство key примет индекс итерации отображения, а элемент (компонент в v.5 react-router-dom) примет <Layout />. Этот Route будет отображаться как <Layout />.

    Дочерний элемент для этого маршрута также является элементом Route, но он будет представлять компонент ProtectedRoute, а свойство элемента примет компонент <ProtectedRoute /> со своими свойствами.

    Внутри компонента ProtectedRoute мы будем генерировать дочерние элементы, используя v.5 react-router-dom, за исключением того, что свойство компонента теперь называется element, как упоминалось ранее.

  2. Константа subRoutes - это наши плоские маршруты, поэтому мы вызываем метод map, чтобы сгенерировать маршруты, которые будут отображаться в качестве дочерних элементов в компоненте ProtectedRoute.

    Теперь вернемся к компоненту <Outlet>, возвращаемому в компоненте ProtectedRoute. Он сообщит react-router-dom, где отображать дочерние элементы.

    Мы сделали то же самое с компонентом Layout.

 
return (
  <Route key={index} element={<Layout />}>
    {/* ... */}
  </Route>
)
 

6 - Обновление содержимого макетов.

В результате мы должны обновить оба макета, чтобы они возвращали <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;
 

7 - Вызов функции генерации маршрутов.

Теперь, когда наш генератор маршрутов готов, вернемся к файлу /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);
  

8 - Реализация системы маршрутизации

Теперь, когда мы подготовили нашу систему маршрутизации, начинается последний шаг.

В файле 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;
 

9 - Заключение

Теперь мы увидели, как работать с несколькими макетами в 6-й версии react-router-dom. Кроме того, мы увидели, как создавать динамические маршруты вместо того, чтобы иметь один файл со всеми маршрутами, добавленными по одному.


Возможно, вам будет интересно

Что нового в react-router v6?

Для тех, кто только начинает изучать React, сталкивается с проблемами организации роутов в приложении, т.к. в интернете много материалов по работе react-router более ранних версий. В данной статье будут описаны основная разница и новшества работы с react-router v6.

Рендеринг нескольких макетов в React с помощью react-router-dom v6.

Пошаговое руководство по рендерингу нескольких макетов в React с использованием новой версии react-router-dom 6.

React useHooks. Часть 1 - полезные React хуки.

Хуки — это функции в React, которые позволяют Вам использовать состояние и другие функции React без написания классов. В этой статье мы будем рассматривать такие хуки, как useToggle, useFirestoreQuery, useMemoCompare, useAsync, useRequireAuth, useRouter, useAuth, useEventListener, useWhyDidYouUpdate, useDarkMode.

Руководство по внедрению Tailwind CSS в React JS

В этом руководстве мы расскажем, как настроить или добавить Tailwind CSS в приложение React JS. Мы также покажем Вам пример, как создать простой компонент с помощью фреймворка Tailwind CSS.

Оформление заявки

Документы на создание сайта

Изучите наше коммерческое предложение, заполните БРИФ и отправьте его на почту maxidebox@list.ru. Изучив все пожелания из БРИФ-а, обратным ответом оповестим Вас по стоимости разработке, ответим на вопросы.

КП на создание сайта Коммерческое предложение на созданеи сайта

Мы берем на себя ответственность за все стадии работы и полностью избавляем клиентов от забот и необходимости вникать в тонкости.

Скачать БРИФ (акета) на создание сайта Скачать БРИФ (акета) на создание сайта

Зополните у БРИФ-а все необходимые поля. Сделайте краткое описание к каждому из пунктов анкеты, привидите примеры в соответсвующий пунктах - это позволит лучше понять Ваши ожидания и требования к сайту