Вложенные маршруты React Router v5.0

Я создаю приложение для реагирования, и я не могу заставить работать маршрутизацию.

  1. Мне нужен один общий макет (верхний и нижний колонтитулы) для нескольких маршрутов аутентификации (/login, sign-up, forgot-password и т. д.)

  2. И мне нужен другой общий макет для остальных защищенных частей приложения (Home, Dashboard и т. д.)

  3. Мне нужна еще одна страница 404 без макета.

Я пробовал несколько методов из этих ссылок:

Но смог дойти до рабочей версии.

Это то, что у меня сейчас есть:

(Примечание: на данный момент я игнорирую необходимость блокировать незарегистрированных пользователей в частных маршрутах AppLayout, я займусь этим сразу после)

const App: React.FC = () => {
    const history = createBrowserHistory();

    return (
        <div className = "App">
            <Router history = {history}>
                <Switch>
                    <AppLayout>
                        <Route path = "/home" component = {HomePage}/>
                        <Route path = "/dashboard" component = {DashboardPage}/>
                        ...
                    </AppLayout>
                    <AuthLayout>
                        <Route path = "/login" component = {LoginPage}/>
                        <Route path = "/sign-up" component = {SignUpPage}/>
                        ...
                    </AuthLayout>
                    <Route path = "*" component = {NotFoundPage} />
                </Switch>
            </Router>
        </div>
    );
};

export default App;

И AuthLayout, и AppLayout просты и похожи на это (только с разными верхним/нижним колонтитулом для каждого):

class AppLayout extends Component {
    render() {
        return (
            <div className = "AppLayout">
                <header>...</header>
                {this.props.children}
                <footer>...</footer>
            </div>
        );
    }
}

export default AppLayout;

Проблема в том, что рендерятся только маршруты из AppLayout. Другие маршруты просто показывают AppLayout header и footer без какого-либо содержимого.

Это версии реакции, которые я использую:

    "react": "^16.8.6",
    "react-dom": "^16.8.6",
    "react-router-dom": "^5.0.0",

Любая помощь будет оценена по достоинству.

Заранее спасибо.

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

Ответы 4

Вы можете попробовать использовать два разных оператора switch для обработки маршрутов Auth и Protected. У меня был аналогичный вариант использования на работе, и мне было удобно иметь два набора блоков переключателей, из которых только один работал одновременно.

const App: React.FC = () => {
    const history = createBrowserHistory();

    return (
        <div className = "App">
            <Router history = {history}>
                {isLoggedIn ? <PrivateRoutes /> : <AuthRoutes />}
            </Router>
        </div>
    );
};


const PrivateRoutes: React.FC = () => {
    return (
        <>
            <Header />
            <Switch>
                <Route path = "/home" component = {HomePage} />
                <Route path = "/dashboard" component = {DashboardPage} />
                <Route path = "*" component = {NotFoundPage} />
            </Switch>
            <Footer />
        </>
    );
};

const AuthRoutes: React.FC = () => {
    return (
        <>
            <Header />
            <Switch>
                <Route path = "/login" component = {LoginPage} />
                <Route path = "/sign-up" component = {SignUpPage} />
                <Route path = "*" component = {NotFoundPage} />
            </Switch>
            <Footer />
        </>
    );
};

Это хорошее решение! Спасибо! В итоге я использовал другое решение, которое я расскажу в своем собственном ответе, но я многому научился у вас!

ET-CS 28.06.2019 22:57

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

Например

Макеты аутентификации могут находиться под /auth, например, вход будет /auth/login, регистрация будет /auth/signup

Макет приложения может находиться под /app, например, панель инструментов будет /app/dashboard, дом будет /app/home

Рабочая демонстрация

Edit hungry-dubinsky-q1l62

App.js

import { Switch, BrowserRouter, Route, Redirect } from "react-router-dom";

function App() {
  return (
    <BrowserRouter>
      <Layouts />
    </BrowserRouter>
  );
}

Layouts.js

const NotFound = () => <h1>Not Found</h1>;

function Layouts() {
  return (
    <Switch>
      <Route path = "/auth" component = {AuthLayout} />
      <Route path = "/app" component = {AppLayout} />
      <Route path = "/" component = {NotFound} />
    </Switch>
  );
}

AuthLayout

const Signup = () => <p>Login</p>;
const Login = () => <p>Sign up</p>;

function AuthLayout() {
  return (
    <div>
      <h1>Auth Layout</h1>
      <Route path = "/auth/signup" exact component = {Signup} />
      <Route path = "/auth/login" exact component = {Login} />
      <Redirect from = "/auth" to = "/auth/login" exact />
    </div>
  );
}

AppLayout

const Home = () => <p>Home</p>;
const Dashboard = () => <p>Dashboard</p>;

function AppLayout() {
  return (
    <div>
      <h1>App Layout</h1>
      <Route path = "/app/home" exact component = {Home} />
      <Route path = "/app/dashboard" exact component = {Dashboard} />
      <Redirect from = "/app" to = "/app/home" exact />
    </div>
  );
}

Кроме того, если вы хотите защитить определенные маршруты от отображения, если они не аутентифицированы, вы можете создать компонент PrivateRoute, который будет перенаправлять на макет аутентификации, если он не аутентифицирован.

PrivateRoute.js

const PrivateRoute = ({ component: Component, ...rest }) => (
  <Route
    {...rest}
    render = {props => sessionStorage.token // your auth mechanism goes here
      ? <Component {...props} />
      : <Redirect to = {{ pathname: '/auth' }} />}
  />
);

Вы можете использовать этот компонент PrivateRoute вместо компонента react-routerRoute.

Например:

<PrivateRoute path = "/app" component = {AppLayout} />

Спасибо! хотя я предпочитаю решение, не ограничивающее меня в изменении URL-адресов (/app,/auth)

ET-CS 28.06.2019 22:56

Это решение помогло мне разобраться. Это лучше, чем принятый ответ imo.

therebelcoder 25.10.2019 18:01

Как человек с опытом работы с angular, это самый разумный и чистый подход к маршрутизации, который я когда-либо видел в React. Слишком много тарабарщины в подходах к маршрутизации React. Это чисто, читабельно, очевидно и легко расширяется. Спасибо

KhoPhi 31.01.2020 11:24

если вы переходите к чему-то вроде /app/somethingnotfound, это не работает, это не переходит к компоненту NotFound

Vencovsky 20.06.2020 16:14

@Vencovsky, удалив точный маршрут из корневого макета, вы сможете перейти к /app/something

rake7h 08.08.2020 09:21

Обновлено: Я ответил на этот вопрос другим решением.


Оба решения от @Gaurab Kc и @johnny peter великолепны, хотя в итоге я сделал что-то вроде этого:

<Router history = {history}>
    <Switch>
        <PrivateRoute
            path = "/home"
            component = {HomePage}>
        </PrivateRoute>
        <PrivateRoute
            path = "/dashboard"
            component = {DashboardPage}>
        </PrivateRoute>
        <AuthRoute
            path = "/login"
            component = {LoginPage}>
        </AuthRoute>
        <AuthRoute
            path = "/sign-up"
            component = {SignUpPage}>
        </AuthRoute>
        <Route path = "*" component = {NotFoundPage}/>
    </Switch>
</Router>

AuthRoute и PrivateRoute примерно так:

interface PrivateRouteProps extends RouteProps {
    component: any;
}

const PrivateRoute = (props: PrivateRouteProps) => {
    const {component: Component, ...rest} = props;

    return (
        <Route
            {...rest}
            render = {(routeProps) =>
                localStorage.getItem('user') ? (
                    <div>
                       ... // here is the app header
                       <Component {...routeProps} />
                       .. // here is the app footer
                    </div>
                ) : (
                    <Redirect
                        to = {{
                            pathname: '/login',
                            state: {from: routeProps.location}
                        }}
                    />
                )
            }
        />
    );
};

export default PrivateRoute;
interface AuthRouteProps extends RouteProps {
    component: any;
}

const AuthRoute = (props: AuthRouteProps) => {
    const {component: Component, ...rest} = props;

    return (
        <Route
            {...rest}
            render = {(routeProps) =>
                (
                    <div>
                       ... // here is the auth header
                       <Component {...routeProps} />
                       .. // here is the auth footer
                    </div>
                )
            }
        />
    );
};

export default AuthRoute;

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

johnny peter 29.06.2019 04:15

@johnnypeter Это правильный и важный момент. Но на данном этапе я бы предпочел заплатить за решение, которое заставляет меня устанавливать маршруты /auth и /app. Буду рад узнать, есть ли другое решение. Спасибо.

ET-CS 29.06.2019 14:29
Ответ принят как подходящий

Мне потребовалось некоторое время, чтобы найти ответ, который мне понравился. Оба решения @johnny-peter и @gaurab-kc были великолепны, и они рассказали мне о механизме маршрутизации React.

Решение @johnny-peter имело тот недостаток, что вынуждало меня ставить префикс для всех связанных маршрутов auth под /auth/... (например, /auth/login и auth/sign-up), чего я не хотел.

Решение @gaurab-kc поддерживало только один набор маршрутов.. поэтому, если пользователь уже был зарегистрирован, он больше не мог посещать маршрут /login.

До недавнего времени я использовал свое собственное решение, у которого была проблема «победить всю цель общего верхнего и нижнего колонтитула». как упомянул @johnny-peter, и за него несколько раз проголосовали, как и должно быть.

Теперь я использую другое решение:

<Router history = {browserHistory}>
    <Switch>
        <Redirect exact from = "/" to = "/home"/>
        <Route exact path = {["/login", "/sign-up", ...]}>
            <AuthLayout>
                <Switch>
                    <Route
                        path = "/login"
                        component = {LoginPage}
                    />
                    <Route
                        path = "/sign-up"
                        component = {SignUpPage}
                    />
                </Switch>
            </AuthLayout>
        </Route>
        <Route exact path = {[
            "/home",
            "/dashboard",
            ...
        ]}>
            <SiteLayout>
                <Switch>
                    <Route
                        path = "/home"
                        component = {HomePage}
                    />
                    <Route
                        path = "/dashboard"
                        component = {DashboardPage}
                    />
                </Switch>
            </SiteLayout>
        </Route>
        <Route path = "*" component = {NotFoundPage}/>
    </Switch>
</Router>

что предотвращает все вышеперечисленные недостатки. Это позволяет мне:

  1. Используйте макет для каждого раздела, который не перерисовывается при изменении маршрута.
  2. Не заставляет меня добавлять какие-либо префиксы к маршрутам.
  3. Все маршруты работают одновременно, что позволяет пользователям вернуться к /login или другим auth маршрутам без предварительного выхода из системы.

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

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