将 React-Router 与布局页面或每页多个组件一起使用

Using React-Router with a layout page or multiple components per page

我正在将 React 路由器添加到现有项目中。

目前传入一个模型到一个根组件,其中包含一个子导航的导航组件和一个主组件。

我发现的 react router 示例只有一个子组件,在不重复布局代码的情况下更改多个子组件的最佳方法是什么?

如果我理解正确的话,要实现这一点,您可以在 Route 中定义多个组件。您可以像这样使用它:

// think of it outside the context of the router, if you had pluggable
// portions of your `render`, you might do it like this
<App children={{main: <Users/>, sidebar: <UsersSidebar/>}}/>

// So with the router it looks like this:
const routes = (
  <Route component={App}>
    <Route path="groups" components={{main: Groups, sidebar: GroupsSidebar}}/>
    <Route path="users" components={{main: Users, sidebar: UsersSidebar}}>
      <Route path="users/:userId" component={Profile}/>
    </Route>
  </Route>
)

class App extends React.Component {
  render () {
    const { main, sidebar } = this.props;
    return (
      <div>
        <div className="Main">
          {main}
        </div>
        <div className="Sidebar">
          {sidebar}
        </div>
      </div>
    )
  }
}

class Users extends React.Component {
  render () {
    return (
      <div>
        {/* if at "/users/123" `children` will be <Profile> */}
        {/* UsersSidebar will also get <Profile> as this.props.children,
            so its a little weird, but you can decide which one wants
            to continue with the nesting */}
        {this.props.children}
      </div>
    )
  }
}

另请查看 sidebar example app,应该对您有更多帮助。

编辑: 根据@Luiz 的评论:

In the latest version of router (v3) the components are in the root of the props object

所以:

const { main, sidebar } = this.props.children;

变成:

const { main, sidebar } = this.props;

编辑: 在 react-router v4 中,这可以像这样完成(根据 new docs 中提供的示例):

import React from 'react'
import {
  BrowserRouter as Router,
  Route,
  Link
} from 'react-router-dom'

// Each logical "route" has two components, one for
// the sidebar and one for the main area. We want to
// render both of them in different places when the
// path matches the current URL.
const routes = [
  { path: '/',
    exact: true,
    sidebar: () => <div>home!</div>,
    main: () => <h2>Home</h2>
  },
  { path: '/bubblegum',
    sidebar: () => <div>bubblegum!</div>,
    main: () => <h2>Bubblegum</h2>
  },
  { path: '/shoelaces',
    sidebar: () => <div>shoelaces!</div>,
    main: () => <h2>Shoelaces</h2>
  }
]

const SidebarExample = () => (
  <Router>
    <div style={{ display: 'flex' }}>
      <div style={{
        padding: '10px',
        width: '40%',
        background: '#f0f0f0'
      }}>
        <ul style={{ listStyleType: 'none', padding: 0 }}>
          <li><Link to="/">Home</Link></li>
          <li><Link to="/bubblegum">Bubblegum</Link></li>
          <li><Link to="/shoelaces">Shoelaces</Link></li>
        </ul>

        {routes.map((route, index) => (
          // You can render a <Route> in as many places
          // as you want in your app. It will render along
          // with any other <Route>s that also match the URL.
          // So, a sidebar or breadcrumbs or anything else
          // that requires you to render multiple things
          // in multiple places at the same URL is nothing
          // more than multiple <Route>s.
          <Route
            key={index}
            path={route.path}
            exact={route.exact}
            component={route.sidebar}
          />
        ))}
      </div>

      <div style={{ flex: 1, padding: '10px' }}>
        {routes.map((route, index) => (
          // Render more <Route>s with the same paths as
          // above, but different components this time.
          <Route
            key={index}
            path={route.path}
            exact={route.exact}
            component={route.main}
          />
        ))}
      </div>
    </div>
  </Router>
)

export default SidebarExample

请务必在此处查看新的 React Router v4 文档:https://reacttraining.com/react-router/

组件可以是 returns JSX 的函数。

  <Route>
    <Route path="/" component={App}>
      <IndexRoute component={Home} />
      <Route path="Invite" component={()=>(<div><Home/><Invite/></div>)} />
    </Route>
  </Route>

2019 +

避免滥用重新渲染的简单明了的方法是(在 react router v5 上测试,需要在 react router v4 上确认):

       <Switch>
         <Route exact path={["/route1/:id/:token", "/"]}>
          <Layout1>
            <Route path="/route1/:id/:token" component={SetPassword} />
            <Route exact path="/" component={SignIn} />
          </Layout1>
        </Route>
        <Route path={["/route2"]}>
          <Layout2>
            <Route path="/route2" component={Home} />
          </Layout2>
        </Route>
      </Switch>

可以重构为:

const routes = [
  {
    layout:Layout1,
    subRoutes:[
      {
        path:"/route1/:id/:token",
        component:SetPassword
      },
      {
        exact:true,
        path:"/",
        component:SignIn
      },
    ]
  },
  {
    layout:Layout2,
    subRoutes:[
      {
        path:"/route2",
        component:Home
      },
    ]
  }
];

与:

      <Switch>
        {routes.map((route,i)=>
          <Route key={i} exact={route.subRoutes.some(r=>r.exact)} path={route.subRoutes.map(r=>r.path)}>
            <route.layout>
              {route.subRoutes.map((subRoute,i)=>
                <Route key={i} {...subRoute} />
              )}
            </route.layout>
          </Route>
        )}
      </Switch>

补充一下 Sebastien 的回答,这似乎对我有用,包括未找到的路由和动态子路由。下面的示例使我的 LayoutAuthenticatedLayoutAnonymous 只渲染一次,而不是在使用相同布局的路由中的每个路由更改上。还添加了 PageSettings 示例以显示此体系结构中的嵌套路由。希望这对其他人有帮助!

(示例包括 TypeScript)

const publicRoutes = [
  {
    key: "login",
    path: "/login",
    component: PageLogin,
    exact: true
  },
  {
    key: "signup",
    path: "/signup",
    component: PageSignup,
    exact: true
  },
  {
    key: "forgot-password",
    path: "/forgot-password",
    component: PageForgotPassword,
    exact: true
  }
];

const privateRoutes = [
  {
    key: "home",
    path: "/",
    component: PageHome,
    exact: true
  },
  {
    key: "settings",
    path: "/settings",
    component: PageSettings, // sub routing is handled in that component
    exact: false // important, PageSettings is just a new Router switch container
  }
];
// Routes.tsx

<Router>
  <Switch>
    <Route exact path={["/", "/settings", "/settings/*"]}>
      <LayoutAuthenticated>
        <Switch>
          {privateRoutes.map(privateRouteProps => (
            <PrivateRoute {...privateRouteProps} />
          ))}
        </Switch>
      </LayoutAuthenticated>
    </Route>

    <Route exact path={["/login", "/signup", "/forgot-password"]}>
      <LayoutAnonymous>
        <Switch>
          {publicRoutes.map(publicRouteProps => (
            <PublicRoute {...publicRouteProps} />
          ))}
        </Switch>
      </LayoutAnonymous>
    </Route>

    <Route path="*">
      <LayoutAnonymous>
        <Switch>
          <Route component={PageNotFound} />
        </Switch>
      </LayoutAnonymous>
    </Route>
  </Switch>
</Router>
// LayoutAnonymous.tsx

import React from 'react';

export const LayoutAnonymous: React.FC<{}> = props => {
  return (
    <div>
      {props.children}
    </div>
  )
}

// LayoutAuthenticated.tsx

import React from 'react';
import { MainNavBar } from '../components/MainNavBar';
import { MainContent } from '../components/MainContent';

export const LayoutAuthenticated: React.FC<{}> = props => {
  return (
    <>
      <MainNavBar />
      <MainContent>
        {props.children}
      </MainContent>
    </>
  )
}


// PrivateRoute.tsx

import React from "react";
import {
  Route,
  Redirect,
  RouteProps
} from "react-router-dom";
import { useSelector } from "react-redux";

interface Props extends RouteProps {}

export const PrivateRoute: React.FC<Props> = props => {
  const isAuthenticated: boolean = useSelector<any, any>((stores) => stores.auth.isAuthenticated);

  const { component: Component, ...restProps } = props;

  if (!Component) return null;

  return (
    <Route
      {...restProps}
      render={routeRenderProps =>
        isAuthenticated ? (
          <Component {...routeRenderProps} />
        ) : (
          <Redirect
            to={{
              pathname: "/login",
              state: { from: routeRenderProps.location }
            }}
          />
        )
      }
    />
  )
}
// PublicRoute.tsx


import React from "react";
import { Route, RouteProps, Redirect } from "react-router-dom";
import { useSelector } from "react-redux";

interface Props extends RouteProps {}

export const PublicRoute: React.FC<Props> = props => {
  const isAuthenticated: boolean = useSelector<any, any>((stores) => stores.auth.isAuthenticated);
  const { component: Component, ...restProps } = props;

  if (!Component) return null;

  return (
    <Route
      {...restProps}
      render={routeRenderProps => (
        !isAuthenticated ? (
          <Component {...routeRenderProps} />
        ) : (
          <Redirect
            to={{
              pathname: "/",
              state: { from: routeRenderProps.location }
            }}
          />
        )
      )}
    />
  )
}

// PageSettings.tsx

import React from "react";
import { LinkContainer } from "react-router-bootstrap";
import Button from "react-bootstrap/Button";
import {
  Switch,
  useRouteMatch,
  Redirect,
  Switch
} from "react-router-dom";

import { PrivateRoute } from "../../routes/PrivateRoute";
import { PageSettingsProfile } from "./profile";
import { PageSettingsBilling } from "./billing";
import { PageSettingsAccount } from "./account";

export const PageSettings = () => {
  const { path } = useRouteMatch();

  return (
    <div>
      <h2>Settings</h2>

      <Redirect strict from={path} to={`${path}/profile`} />

      <LinkContainer to={`${path}/profile`}>
        <Button>Profile</Button>
      </LinkContainer>
      <LinkContainer to={`${path}/billing`}>
        <Button>Billing</Button>
      </LinkContainer>
      <LinkContainer to={`${path}/account`}>
        <Button>Account</Button>
      </LinkContainer>

      <Switch>
        <PrivateRoute path={`${path}/profile`} component={PageSettingsProfile} />
        <PrivateRoute path={`${path}/billing`} component={PageSettingsBilling} />
        <PrivateRoute path={`${path}/account`} component={PageSettingsAccount} />
      </Switch>
    </div>
  );
};

你可以直接在 Router 标签内使用两个 switch 语句,而不是那么混乱。`

    <div className= {classes.root}>
      <CssBaseline></CssBaseline>
      <Router>
      <Switch>
        <Route path="/" exact component={Header}></Route>
        <Route path="/login" component={Login}></Route>
      </Switch>
      <Switch>
        <Route path="/" exact component={Checkout}></Route>
      </Switch>
      </Router> 
    </div>
这将解决您的两个组件一个位于另一个下方的问题。

从 React Router v6 开始,所有当前答案都已过时,现在更容易了。

基本布局和身份验证示例在文档中。

https://reactrouter.com/docs/en/v6/examples/basic