将 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 的回答,这似乎对我有用,包括未找到的路由和动态子路由。下面的示例使我的 LayoutAuthenticated
和 LayoutAnonymous
只渲染一次,而不是在使用相同布局的路由中的每个路由更改上。还添加了 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 开始,所有当前答案都已过时,现在更容易了。
基本布局和身份验证示例在文档中。
我正在将 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 的回答,这似乎对我有用,包括未找到的路由和动态子路由。下面的示例使我的 LayoutAuthenticated
和 LayoutAnonymous
只渲染一次,而不是在使用相同布局的路由中的每个路由更改上。还添加了 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 开始,所有当前答案都已过时,现在更容易了。
基本布局和身份验证示例在文档中。