Как создать адаптивное главное подробное приложение с помощью React Router?

Я подготовил простой тестовый пример на Github для моего вопроса:

В RouterLayout.jsx медиа-запрос заставляет Outlet исчезать на узких экранах, как вы можете видеть на анимированном снимке экрана, где я перетаскиваю сторону браузера, чтобы уменьшить его:

RootLayout.jsx

import { Outlet } from "react-router";
import { useMediaQuery } from "@react-hook/media-query";
import MasterList from "../components/MasterList";

export default function RootLayout() {
  const isSmallScreen = useMediaQuery("(max-width: 768px)");

  return (
    <div
      style = {{
        display: "flex",
        justifyContent: "space-around",
        alignItems: "flex-start",
      }}
    >
      <MasterList />
      {!isSmallScreen && (
        <div>
          <h3>Detail</h3>
          <Outlet />
        </div>
      )}
    </div>
  );
}

Мой вопрос, пожалуйста:

На маленьких экранах мобильных телефонов должен отображаться только основной список. И когда там щелкают ссылку, должна отображаться страница 1 или другие страницы с возможностью вернуться обратно в основной список. Это пока не работает.

На больших экранах планшетов и компьютеров основной список и страница 1 или другие отображаются рядом друг с другом. Это уже работает в моем коде.

Должен ли я добавить какой-нибудь атрибут к Link в файле MasterList.jsx ниже?

MasterList.jsx

import { Link } from "react-router-dom";

const MasterList = () => {
  const pages = [
    { id: 1, title: "Page 1" },
    { id: 2, title: "Page 2" },
    { id: 3, title: "Page 3" },
    { id: 4, title: "Page 4" },
  ];

  return (
    <div>
      <h2>Master List</h2>
      <nav>
        <ul>
          {pages.map((page) => (
            <li key = {page.id}>
              <Link to = {"/page" + page.id}>{page.title}</Link>
            </li>
          ))}
        </ul>
      </nav>
    </div>
  );
};

export default MasterList;

Приложение.jsx

import {
  Route,
  RouterProvider,
  createBrowserRouter,
  createRoutesFromElements,
} from "react-router-dom";
import Page1 from "./pages/Page1";
import Page2 from "./pages/Page2";
import Page3 from "./pages/Page3";
import Page4 from "./pages/Page4";
import RootLayout from "./layouts/RootLayout";

const router = createBrowserRouter(
  createRoutesFromElements(
    <Route path = "/" element = {<RootLayout />}>
      <Route path = "page1" element = {<Page1 />} />
      <Route path = "page2" element = {<Page2 />} />
      <Route path = "page3" element = {<Page3 />} />
      <Route path = "*" element = {<Page4 />} />
    </Route>
  )
);

const App = () => {
  return <RouterProvider router = {router} />;
};

export default App;

Выходы используются для визуализации дочерних маршрутов, поэтому имеет смысл, чтобы дочерний компонент не отображался, если вы не визуализируете выход. Можете ли вы более четко описать, чего вы пытаетесь достичь? Что и по какому пути вы хотите отображать на маленьких или больших экранах?

Alexander Wiklund 23.04.2024 15:50

Пожалуйста, измените заголовок, чтобы задать что-то более конкретное. Как создать сложное приложение — очень обширная тема. Ограничьте круг вашей актуальной проблемой, чтобы нам всем не приходилось бродить сюда, чтобы выяснить, что вам действительно нужно. См. Как спросить.

isherwood 23.04.2024 18:24
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
4
2
104
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я не знаю, какой лучший подход для достижения этой цели, но он должен учитывать тот факт, что Outlet отображает дочерние маршруты в родительском компоненте.

Я бы изучил useMatches, чтобы узнать, можно ли использовать его для определения того, был ли родительский компонент отображен на /parent или /parent/child. С помощью этой информации вы можете условно визуализировать элементы навигации:

  • Если включен /parent, всегда отображать навигацию.
  • Если в /parent/child экран маленький, не отображайте навигацию.
  • Всегда отображать Outlet
Ответ принят как подходящий

При меньших размерах представления, где Outlet не отображается, у вложенных Route компонентов больше нет места для отображения element содержимого. Если я правильно понимаю ваше сообщение, вы хотите использовать RootLayout и Outlet в больших размерах просмотра, не «мобильных», а в размерах просмотра для мобильных устройств отображать RootLayout как родственный маршрут вместо родительского маршрута, чтобы все остальные маршруты были доступны.

Пример рефакторинга:

  • Создайте два корневых макета: мобильный и немобильный.

    function RootLayout() {
      return (
        <div
          style = {{
            display: "flex",
            justifyContent: "space-around",
            alignItems: "flex-start"
          }}
        >
          <MasterList />
          <div>
            <h3>Detail</h3>
            <Outlet />
          </div>
        </div>
      );
    }
    
    function MobileLayout() {
      return (
        <div
          style = {{
            display: "flex",
            justifyContent: "space-around",
            alignItems: "flex-start"
          }}
        >
          <div>
            <h3>Detail</h3>
            <Outlet />
          </div>
        </div>
      );
    }
    
  • Обновите App, чтобы использовать хук медиа-запроса и условно отображать тот или иной корневой макет, а также условно отображать MasterList как индексный маршрут на экранах меньшего размера.

    export default function App() {
      const isSmallScreen = useMediaQuery("(max-width: 768px)");
    
      const router = React.useMemo(
        () =>
          createBrowserRouter(
            createRoutesFromElements(
              <Route
                path = "/"
                element = {isSmallScreen ? <MobileLayout /> : <RootLayout />}
              >
                {isSmallScreen && <Route index element = {<MasterList />} />}
                <Route path = "page1" element = {<Page1 />} />
                <Route path = "page2" element = {<Page2 />} />
                <Route path = "page3" element = {<Page3 />} />
                <Route path = "*" element = {<Page4 />} />
              </Route>
            )
          ),
        [isSmallScreen]
      );
    
      return <RouterProvider router = {router} />;
    }
    

Альтернатива

Альтернативой может быть использование хука useMatch, чтобы проверить, является ли корень "/" текущим совпадающим маршрутом, и отрендерить MasterList. Логика немного более запутанная и немного менее СУХАЯ. Суть здесь в том, чтобы отображать обычные MasterList и Outlet для больших размеров представления, а в мобильных представлениях условно отображать MasterList, только если на домашней странице, в противном случае визуализируйте Outlet для вложенных маршрутов.

Пример:

import { Outlet, useMatch } from 'react-router-dom';
...

function RootLayout() {
  const isSmallScreen = useMediaQuery("(max-width: 768px)");
  const isHome = useMatch("/");

  return (
    <div
      style = {{
        display: "flex",
        justifyContent: "space-around",
        alignItems: "flex-start"
      }}
    >
      {isSmallScreen ? (
        isHome ? (
          <MasterList />
        ) : (
          <div>
            <h3>Detail</h3>
            <Outlet />
          </div>
        )
      ) : (
        <>
          <MasterList />
          <div>
            <h3>Detail</h3>
            <Outlet />
          </div>
        </>
      )}
    </div>
  );
}
const router = createBrowserRouter(
  createRoutesFromElements(
    <Route path = "/" element = {<RootLayout />}>
      <Route path = "page1" element = {<Page1 />} />
      <Route path = "page2" element = {<Page2 />} />
      <Route path = "page3" element = {<Page3 />} />
      <Route path = "*" element = {<Page4 />} />
    </Route>
  )
);

export default function App() {
  return <RouterProvider router = {router} />;
}

Другие вопросы по теме