具有多种布局的 React Router v4
Posted
技术标签:
【中文标题】具有多种布局的 React Router v4【英文标题】:React Router v4 with multiple layouts 【发布时间】:2017-08-09 06:39:11 【问题描述】:我想在我的公共布局中渲染我的一些路线,在我的私人布局中渲染一些其他路线,有没有一种干净的方法可以做到这一点?
显然不起作用的示例,但我希望大致说明我在寻找什么:
<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>
我想要切换某些路由的布局,我该如何使用新的 react 路由器来做到这一点?
嵌套路由不再起作用并给我这个错误:
You should not use <Route component> and <Route children> in the same route; <Route children> will be ignored
编辑:让布局包裹整个路由组也意味着只要您留在同一个私有/公共路由组中,这些布局只会渲染一次。例如,如果您的布局必须从服务器获取某些内容,这将是一件大事,因为如果您使用布局包装每个页面,那么每次页面更改都会发生这种情况。
【问题讨论】:
在此对话:github.com/ReactTraining/react-router/issues/3928 教程在这里:simonsmith.io/reusing-layouts-in-react-router-4 你能找到一种干净的方法吗? 使用简单的帮助库npmjs.com/package/react-router4-with-layouts 【参考方案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>
【讨论】:
有趣...为什么它不起作用:<Switch><PublicLayout> <Route path="/" component=HomePage/> <Route path="/about" component=AboutPage/></PublicLayout> <PrivateLayout><Route path="/profile" component=ProfilePage/> <Route path="/dashboard" component=DashboardPage/></PrivateLayout> </Switch>
@carkod all exact
属性传递给 RouteWithLayout
:<Switch><RouteWithLayout exact ... >
。直接将其传递给 Route
孩子是行不通的。我的猜测是 <Switch>
只考虑它自己的直接孩子的 exact
道具,即使它不是 Route
组件而是包装器。
这里最大的不便是您的布局会为您访问的每条路线重新渲染。如果您的 Switch
嵌套在布局中,性能会更高。【参考方案2】:
2020 年更新
好吧,现在我正在遵循这种方法,它比我之前发布的更简单:
const Pages = () =>
return (
<ReactRouter>
<Switch>
<Route path="/comingsoon" component=ComingSoon exact />
<Route>
<MainLayout>
<Switch>
<Route path="/home" exact>
<Home />
</Route>
<Route path="/login" exact>
<Login />
</Route>
<Route path="/useraccount" exact>
<UserAccount />
</Route>
<Route path="/createaccount" exact>
<CreateAccount />
</Route>
<Route path="/contact" exact>
<Contact />
</Route>
<Route path="/about" exact>
<About />
</Route>
<Redirect path="/" exact to="/comingsoon" />
<Route path="*" exact component=NotFound />
</Switch>
</MainLayout>
</Route>
</Switch>
</ReactRouter>
);
;
这样,MainLayout 将处理除即将到来的页面之外的所有内容。
老答案
如果您使用 Typescript 并想关注this react layout 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>;
【讨论】:
请接受这个作为正确答案。它很干净,遵循当前的 React 模式并使用 Typescript。 不是每次更改路由都要重新挂载DefaultLayout组件吗?它可能会对整个应用程序产生很大的性能影响,并使得在路线变化时执行动画变得更加困难。 最终使用了 React Router 文档中描述的技术,非常适合这个用例 reacttraining.com/react-router/web/example/sidebar 它工作了,谢谢!【参考方案3】: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>
同样,它可以被重构, 查看我的完整答案: https://***.com/a/57358661/3437790
【讨论】:
【参考方案4】:我认为布局不属于路由文件。
保持路线干净,即:
<Route exact path="/" component="HomePage" />
然后,在 HomePage
组件中,将呈现的内容包装在您选择的布局中:
...
render()
<PublicLayout>
<h1>Home page!</h1>
</PublicLayout>
这样,路线保持超级干净,而且您可以轻松地显示路线,该路线应支持两种布局(例如 404 页面)。
【讨论】:
想象一个应用程序有几十个或上百个路线,这意味着您必须为每条路线重复一次包装布局。只是想保持干燥。 嗯,在这两种情况下它都是一个组件的名称。使用包装器,您可以获得一些额外的好处:您可以让他们将道具传递给孩子;您将它们全部保存在一个文件中;下一个打开该文件的开发人员会立即知道发生了什么。 这应该是公认的答案。是的,如果您有大量路由,您将有一些重复的代码(我们每次只讨论 3 行 tbh)但它有一些优点:单独的布局和路由(这是 2 个不同的关注点),更具声明性( React 方式),更少的额外包装器 s。顺便说一句,大多数应用程序不会处理超过一百条路线,即使那样,布局也不会是你的第一个问题:) @ClementParis016 我完全同意你的看法,我也有同样的想法,重复的代码不会太多,但这可以让你分离关注点! 我不认为这是“更多的 React 方式”,因为 ReactRouter 培训人员写了一个清晰的例子,如何使用渲染函数来做到这一点,正如 Zaptree reacttraining.com/react-router/core/api/Route/render-func 上面提到的那样。【参考方案5】:如果您在其他地方看到过这个问题,我将在任何地方写下这个问题,非常抱歉。 我这样做只是因为我已经挣扎了一段时间,我认为值得尽可能地传播这个解决方案。 足够的话,这就是我所做的,我认为这很不言自明。就像一个魅力,它很容易打字。
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>;
【讨论】:
感谢分享,对我帮助很大。【参考方案6】:我尝试了 Florians 的回答,但这对我来说还不够,因为 react 将为每条路线创建一个单独的布局实例,因此任何导航转换(如标签滑动)都会被终止。
我希望有一个更优雅的解决方案,但这让我的应用再次与 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 或许可以用于更清晰的表示,不确定。
【讨论】:
【参考方案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>
旧:一种解决方案是使用每个 Route 的 render prop,但看起来确实很麻烦:
<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 语法并添加了检查,所以它可以用来代替 react-router 的 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 组件的 render prop,这不会在每次路由更改时卸载和安装您的布局组件。有关其工作原理的更多详细信息here。
假设你有两个布局组件 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 路由器中,我注意到如果您将交换机内的根路径作为第一条路由,它将始终与它匹配,因此永远不会显示您的后续路由。你应该把根路径放在最后。
<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>
【讨论】:
能否请您指定您所指的 React Router 的版本。 我使用的是版本 4 如果你仍然有这个问题,我最终没有使用 Switch,而是创建了 DefaultLayout 和 PrivateLabel 对象。然后从路由器我调用与@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>
【讨论】:
我证明了,但是它同时渲染了两个布局!,你能分享一下你的布局代码吗? @DavidNoreña 我使用相同的代码,但它不会呈现两种布局。 是的,它渲染了两种布局,但隐藏起来非常快,如果你把 console.log 放在这两种布局的渲染函数中,你就会明白我的意思......这是一种奇怪的行为...... .以上是关于具有多种布局的 React Router v4的主要内容,如果未能解决你的问题,请参考以下文章
react-router-dom@6 的布局路由无法按预期工作
javascript 使用React Router v4的多个布局