使用带有布局页面或每页多个组件的 React-Router

Posted

技术标签:

【中文标题】使用带有布局页面或每页多个组件的 React-Router【英文标题】:Using React-Router with a layout page or multiple components per page 【发布时间】:2016-01-08 20:27:21 【问题描述】:

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

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

我发现的 react router 的例子只有一个子组件,有什么最好的方法来改变多个子组件而不重复两个子组件的布局代码?

【问题讨论】:

你好汤姆,只是想知道你是否找到了解决办法?找了两天试了直接导入一个组件还是不行。 【参考方案1】:

如果我对您的理解正确,您可以在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 的评论:

在最新版本的路由器 (v3) 中,组件位于 props 对象的根目录中

所以:

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/

【讨论】:

在最新版本的路由器中,组件位于 props 对象的根目录中。 如何按照 v4 示例获取组件的动态路由参数?这在文档中没有概述。 @Faust 你能举个例子说明你的意思吗?我会尽力帮忙的? @knowbody:我刚刚为此发布了一个问题:***.com/questions/47255363/… 如何将它与最后的全部 404 结合起来。我通常会有一个开关块,最后有一条未指定的路线。更改为此方法我无法使用 switch 块,因为这将导致仅呈现侧边栏,并且如果没有开关,则 404 页面会在每个路径上呈现。【参考方案2】:

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>

【讨论】:

我真的很喜欢这种方法,但我无法实现像 404 页面这样的默认重定向。您将如何将其添加到此解决方案中? @MathisWitte 我想你可以在最后添加一个 ,就在你关闭 标签之前。请注意,您之前没有与之匹配的路线,例如带有 path="/" 没有“exact”属性的路线 @MathisWitte - 我也很难做到这一点。最后,我添加了另一个嵌套的 Switch 组件(在 Layout2 之后),似乎可以解决问题。【参考方案3】:

补充塞巴斯蒂安的回答,这似乎对我有用,包括未找到的路线和动态子路线。下面的示例使我的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>
  );
;

【讨论】:

真的很有用,可以对 Route exact path=["/", "/settings", "/settings/*"] 进行任何改进 谢谢,添加私有路由的聪明方法。 if (!Component) return null;这一行,不应该是component(带小写c)吗?另外,关于公共路由的问题:如果用户经过身份验证,为什么会被重定向?他们不应该能够在不注销的情况下看到该页面吗? @Théophile:我在这里将component 重命名为Componentconst component: Component, ...restProps = props;。这是不久前的代码,但我认为用 PascalCase 编写它可以确保 linter 喜欢它,并允许区分 React 组件和实例/变量/道具。 @JordyvandenAardweg 哦,我明白了,我把它当作一种类型来阅读,就像在const component: React.Component 中一样。现在有道理了。谢谢!【参考方案4】:

组件可以是返回 JSX 的函数。

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

【讨论】:

【参考方案5】:

你可以直接在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的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Vuetify DataTable 组件中设置初始“每页行数”值?

带有 % extends 的每页上的 Django 页脚和页眉

jquery jqprint 打印 每页控制打印内容,每一页都带有表头,怎么搞啊

常见页面布局方式(三种框架集)

带有 Bootstrap 的 React 组件的页面布局 - 页脚未保留在原位

Next.js 的每页布局组件没有从 Vercel 的 swr 全局配置中获得价值