React Router v4 с несколькими макетами

Я бы хотел отобразить некоторые из моих маршрутов в своем общедоступном макете и некоторые другие маршруты в моем личном макете, есть ли чистый способ сделать это?

Пример, который, очевидно, не работает, но я надеюсь, что примерно то, что я ищу:

<Router>

  <PublicLayout>
    <Switch>
      <Route exact path="/" component={HomePage} />
      <Route exact path="/about" component={AboutPage} />
    </Switch>
  </PublicLayout>

  <PrivateLayout>
    <Switch>
      <Route exact path="/profile" component={ProfilePage} />
      <Route exact path="/dashboard" component={DashboardPage} />
    </Switch>
  </PrivateLayout>

</Router>

Я бы хотел, чтобы макет переключался на определенные маршруты, как мне это сделать с новым маршрутизатором?

Вложенные маршруты больше не работают и дают мне эту ошибку:

You should not use <Route component> and <Route children> in the same route; <Route children> will be ignored

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

Ответы

Ответ 1

То, что я сделал для этого, - это создать простой компонент, который добавляет дополнительное свойство к компоненту Route, который является макетом:

function RouteWithLayout({layout, component, ...rest}){
  return (
    <Route {...rest} render={(props) =>
      React.createElement( layout, props, React.createElement(component, props))
    }/>
  );
}

Тогда в вашем случае ваши маршруты будут выглядеть так:

<Switch>
    <RouteWithLayout layout={PublicLayout} path="/" component={HomePage}/>
    <RouteWithLayout layout={PublicLayout} path="/about" component={AboutPage}/>
    <RouteWithLayout layout={PrivateLayout} path="/profile" component={ProfilePage}/>
    <RouteWithLayout layout={PrivateLayout} path="/dashboard" component={DashboardPage}/>
</Switch>

Ответ 2

Если вы используете Typescript и хотите следовать этот ответный макет aproach, то вы можете объявить свой расположение:

import './Default.less';

import React from 'react';
import { Route } from "react-router-dom";

import { Sider } from './Components';
import { Notification } from 'Client/Components';

interface IDefaultProps {
  component: any
  path?: string;
  exact?: boolean;
}

const Default: React.SFC<IDefaultProps> = (props) => {
  const { component: Component, ...rest } = props;
  return <Route {...rest} render={matchProps => (
    <div className="defaultLayout">
      <Sider />
      <div className="defaultLayoutContent">
        <Component {...matchProps} />
      </div>
      <Notification />
    </div>
  )} />
}

export default Default;

И объявите маршруты следующим образом:

import React from 'react';
import { Route } from 'react-router-dom';

import { DefaultLayout } from 'Client/Layout';
import { Dashboard, Matters, Schedules, Students } from './Containers';

export const routes = <div>
  <DefaultLayout exact path="/" component={Dashboard} />
  <DefaultLayout path="/matters" component={Matters} />
  <DefaultLayout path="/schedules" component={Schedules} />
  <DefaultLayout path="/students" component={Students} />
</div>;

Ответ 3

Я не думаю, что макеты принадлежат файлам маршрутов.

Сохраняйте маршрут в чистоте, т.е.

<Route exact path="/" component="HomePage" />

Затем в компоненте HomePage оберните отображаемый контент в ваш макет выбора:

...
render() {
  <PublicLayout>
    <h1>Home page!</h1>
  </PublicLayout>
}

Таким образом, маршруты остаются очень чистыми, плюс у вас есть простой способ отображения маршрутов, которые должны поддерживать оба макета (например, 404 страницы).

Ответ 4

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

const withLayout = (LayoutComp, ComponentComp) => <LayoutComp><ComponentComp /></LayoutComp>;
const withDefaultLayout = (ComponentComp) => () => withLayout(Layout, ComponentComp);
const withEmptyLayout = (ComponentComp) => () => withLayout(EmptyLayout, ComponentComp);

export const routes = <div>
    <Switch>
        <Route path="/" exact render={withDefaultLayout(Home)} />
        <Route path='/subscriptions' render={withDefaultLayout(SubscriptionsWrapped)} />

        <Route path='/callback' render={withEmptyLayout(AuthCallback)} />
        <Route path='/welcome/initial' render={withEmptyLayout(Start)} />
    </Switch>
</div>;

Ответ 5

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

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

Определите один маршрут, указывающий на компонент, который решит, что обернуть маршрут в

<Router>
  <Route component={AppWrap} />
</Router>

В приложении AppWrap выполните следующие действия:

 var isPrivateLayout;
 for (var path of listOfPrivateLayoutPaths) {
   if (this.props.location.pathname == path) {
     isPrivateLayout= true;
   }
 }
 if (isPrivateLayout) {
   return <PrivateLayout>
        (routes)
      </PrivatelyLayout>
 } else {
   return <PublicLayout>
        (routes)
      </PublicLayout>;
 }

Route Config, возможно, может быть использован для более чистого представления этого, не уверен.

Ответ 6

2019+

После поиска, это чистый и эффективный способ (избегая оскорбительного повторного рендеринга):

    <Route exact path={["/about", "/"]}>
      < PublicLayout >
        <Route exact path="/" component={HomePage} />
        <Route path="/about" component={AboutPage} />
      </PublicLayout >
    </Route>
    <Route path={["/profile", "/dashboard"]}>
      < PrivateLayout >
        <Route path="/profile" component={ProfilePage} />
        <Route path="/dashboard" component={DashboardPage} />
      </PrivateLayout >
    </Route>

также, это может быть рефакторинг, см мой полный ответ:fooobar.com/questions/125266/...

Ответ 7

Обновление: я решил это по-другому, но если вы заставляете пространство имен использовать разные части вашего приложения с помощью /app или/admin, например.

Каждый из компонентов UserRoutes, AdminRoutes и PublicRoutes - это в основном большие компоненты Switch с определенным макетом в своем корне.

Вот как это выглядит:

<Router>
  <Switch>
    <Route path="/app" render={props => <UserRoutes {...props} />} />
    <Route path="/admin" render={props => <AdminRoutes {...props} />} />
    <Route path="/" render={props => <PublicRoutes {...props} />} />
  </Switch>
</Router>

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

<Router>
  <Switch>
    <Route
      path="/"
      render={() => <PublicLayout><HomePage /></PublicLayout>}
    />
    <Route
      path="/about"
      render={() => <PublicLayout><AboutPage /></PublicLayout>}
    />
    <Route
      path="/profile"
      render={() => <PrivateLayout><ProfilePage /></PrivateLayout>}
    />
    <Route
      path="/dashboard"
      render={() => <PrivateLayout><DashboardPage /></PrivateLayout>}
    />
  </Switch>
</Router>

Ответ 8

Идея такая же, как у Zaptree, но с использованием синтаксиса es6 и добавленных проверок, чтобы его можно было использовать вместо компонента Route Route-маршрутизатора

Создайте новый компонент, скажем /src/components/Route/index.js:

import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {Route as ReactRoute} from 'react-router'

class Route extends Component {
  static propTypes = {
    component: PropTypes.func.isRequired,
    layout: PropTypes.func,
    path: PropTypes.string,
    exact: PropTypes.bool
  }

  render = () => {
    const {component, layout, path, exact} = this.props
    let routeComponent = props => React.createElement(component, props)

    if (layout) {
      routeComponent = props =>
        React.createElement(layout, props, React.createElement(component, props))
    }

    return <ReactRoute path={path} exact={exact} render={routeComponent}/>
  }
}

export default Route

Используйте созданный компонент Route:

import Route from 'components/Route/'
...

<Router history={createHistory()}>
  <Switch>
    <Route exact path='/' layout={PublicLayout} component={HomePage}/>
    <Route exact path='/' layout={PrivateLayout} component={ProfilePage}/>
    <Route path='/logins' component={Login}/>
  </Switch>
</Router>

Ответ 9

Используйте опору рендеринга компонента Route, это не размонтирует и не монтирует ваш компонент макета при каждом изменении маршрута. Подробнее о том, как это работает, здесь.

Допустим, у вас есть два компонента макета: Primary.js и Secondary.js

.В своем методе рендеринга App.js вы просто возвращаете

<BrowserRouter>
   <Switch>
     <Route path='/login' render={() => <Secondary><Login/></Secondary>} />
     <Route path='/dashboard' render={() => <Primary><Dashboard/></Primary>} />
   </Switch>
</BrowserRouter>

Чтобы еще больше уточнить его, вы также можете определить компонент компоновки компонента высшего порядка, чтобы обернуть компонент страницы макетом. (Не проверено)

<Route to='/login' render={() => Secondary(Login)}

Ответ 10

@Qop верен, однако в новом React Router я заметил, что если у вас есть корневой путь внутри коммутатора в качестве первого маршрута, он всегда будет соответствовать ему и, следовательно, никогда не будет отображать ваши следующие маршруты. Вы должны поставить корневой путь в конце.

<Switch>
    <RouteWithLayout layout={PublicLayout} path="/about" component={AboutPage}/>
    <RouteWithLayout layout={PrivateLayout} path="/profile" component={ProfilePage}/>
    <RouteWithLayout layout={PrivateLayout} path="/dashboard" component={DashboardPage}/>
    <RouteWithLayout layout={PublicLayout} path="/" component={HomePage}/>
</Switch>

Ответ 11

Такая же идея с @Zaptree

Разметка

function PublicLayout(props) {
  return (
      <Route {...props} />
  );
}

function PrivateLayout(props) {
  return (
      <Route {...props} />
  );
}

Маршруты

<Switch>
    <PublicLayout exact path="/" component={HomePage} />
    <PrivateLayout path="/profile" component={ProfilePage} />
    <Route path="/callback" component={NoLayoutPage} />
</Switch>

Ответ 12

Это решение будет работать.

<Router>
  <Switch>
    <PublicLayout>
      <Route exact path="/" component={HomePage} />
      <Route exact path="/about" component={AboutPage} />
    </PublicLayout>
  </Switch>       

  <Switch>
    <PrivateLayout>
      <Route exact path="/profile" component={ProfilePage} />
      <Route exact path="/dashboard" component={DashboardPage} />
    </PrivateLayout>
  </Switch>    
</Router>