可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I am currently struggling with nesting routes using react router v4.
The closest example was the route config in the
React-Router v4 Documentation.
I want to split my app in 2 different parts.
A frontend and an admin area.
I was thinking about something like this:
<Match pattern=\"/\" component={Frontpage}>
<Match pattern=\"/home\" component={HomePage} />
<Match pattern=\"/about\" component={AboutPage} />
</Match>
<Match pattern=\"/admin\" component={Backend}>
<Match pattern=\"/home\" component={Dashboard} />
<Match pattern=\"/users\" component={UserPage} />
</Match>
<Miss component={NotFoundPage} />
The frontend has a different layout and style than the admin area. So within the frontpage the route home, about and so one should be the child routes.
/home should be rendered into the Frontpage component and /admin/home should be rendered within the Backend component.
I tried some variations but I always ended in not hitting /home or /admin/home.
Edit - 19.04.2017
Because this post has a lot of views right now I updated it with the final solution. I hope it helps someone.
Edit - 08.05.2017
Removed old solutions
Final solution:
This is the final solution I am using right now. This example also has a global error component like a traditional 404 page.
import React, { Component } from \'react\';
import { Switch, Route, Redirect, Link } from \'react-router-dom\';
const Home = () => <div><h1>Home</h1></div>;
const User = () => <div><h1>User</h1></div>;
const Error = () => <div><h1>Error</h1></div>
const Frontend = props => {
console.log(\'Frontend\');
return (
<div>
<h2>Frontend</h2>
<p><Link to=\"/\">Root</Link></p>
<p><Link to=\"/user\">User</Link></p>
<p><Link to=\"/admin\">Backend</Link></p>
<p><Link to=\"/the-route-is-swiggity-swoute\">Swiggity swooty</Link></p>
<Switch>
<Route exact path=\'/\' component={Home}/>
<Route path=\'/user\' component={User}/>
<Redirect to={{
state: { error: true }
}} />
</Switch>
<footer>Bottom</footer>
</div>
);
}
const Backend = props => {
console.log(\'Backend\');
return (
<div>
<h2>Backend</h2>
<p><Link to=\"/admin\">Root</Link></p>
<p><Link to=\"/admin/user\">User</Link></p>
<p><Link to=\"/\">Frontend</Link></p>
<p><Link to=\"/admin/the-route-is-swiggity-swoute\">Swiggity swooty</Link></p>
<Switch>
<Route exact path=\'/admin\' component={Home}/>
<Route path=\'/admin/user\' component={User}/>
<Redirect to={{
state: { error: true }
}} />
</Switch>
<footer>Bottom</footer>
</div>
);
}
class GlobalErrorSwitch extends Component {
previousLocation = this.props.location
componentWillUpdate(nextProps) {
const { location } = this.props;
if (nextProps.history.action !== \'POP\'
&& (!location.state || !location.state.error)) {
this.previousLocation = this.props.location
};
}
render() {
const { location } = this.props;
const isError = !!(
location.state &&
location.state.error &&
this.previousLocation !== location // not initial render
)
return (
<div>
{
isError
? <Route component={Error} />
: <Switch location={isError ? this.previousLocation : location}>
<Route path=\"/admin\" component={Backend} />
<Route path=\"/\" component={Frontend} />
</Switch>}
</div>
)
}
}
class App extends Component {
render() {
return <Route component={GlobalErrorSwitch} />
}
}
export default App;
回答1:
In react-router-v4 you don\'t nest <Routes />
. Instead, you put them inside another <Component />
.
For instance
<Route path=\'/topics\' component={Topics}>
<Route path=\'/topics/:topicId\' component={Topic} />
</Route>
should become
<Route path=\'/topics\' component={Topics} />
with
const Topics = ({ match }) => (
<div>
<h2>Topics</h2>
<Link to={`${match.url}/exampleTopicId`}>
Example topic
</Link>
<Route path={`${match.url}/:topicId`} component={Topic}/>
</div>
)
Here is a basic example straight from the react-router documentation.
回答2:
Just wanted to mention react-router v4 changed radically since this question was posted/answed.
There is no <Match>
component any more! <Switch>
is to make sure only the first match is rendered. <Redirect>
well .. redirects to another route. Use or leave out exact
to either in- or exclude a partial match.
See the docs. They are great. https://reacttraining.com/react-router/
Here\'s an example I hope is useable to answer your question.
<Router>
<div>
<Redirect exact from=\'/\' to=\'/front\'/>
<Route path=\"/\" render={() => {
return (
<div>
<h2>Home menu</h2>
<Link to=\"/front\">front</Link>
<Link to=\"/back\">back</Link>
</div>
);
}} />
<Route path=\"/front\" render={() => {
return (
<div>
<h2>front menu</h2>
<Link to=\"/front/help\">help</Link>
<Link to=\"/front/about\">about</Link>
</div>
);
}} />
<Route exact path=\"/front/help\" render={() => {
return <h2>front help</h2>;
}} />
<Route exact path=\"/front/about\" render={() => {
return <h2>front about</h2>;
}} />
<Route path=\"/back\" render={() => {
return (
<div>
<h2>back menu</h2>
<Link to=\"/back/help\">help</Link>
<Link to=\"/back/about\">about</Link>
</div>
);
}} />
<Route exact path=\"/back/help\" render={() => {
return <h2>back help</h2>;
}} />
<Route exact path=\"/back/about\" render={() => {
return <h2>back about</h2>;
}} />
</div>
Hope it helped, let me know. If this example is not answering your question well enough, tell me and I\'ll see if I can modify it.
回答3:
It\'s true that in order to nest Routes you need to place them in the child component of the Route.
However if you prefer a more \'inline\' syntax rather than breaking your Routes up across components, you can utilise the render
prop on the <Route>
you want to nest under, with an inline stateless functional component, like so:
<BrowserRouter>
<Route exact path=\"/\" component={Frontpage} />
<Route path=\"/home\" component={HomePage} />
<Route path=\"/about\" component={AboutPage} />
<Route
path=\"/admin\"
render={({ match: { url } }) => (
<>
<Route exact path={`${url}/`} component={Backend} />
<Route path={`${url}/home`} component={Dashboard} />
<Route path={`${url}/users`} component={UserPage} />
</>
)}
/>
</BrowserRouter>
If you\'re interested in why render
should be used instead of component
, it\'s to stop the inline component from being remounted on every render. See the documentation for more detail.
Also note that the above example uses React 16 Fragments to wrap the nested Routes, just to make it a bit cleaner. Prior to React 16 you could just use a container <div>
instead here.
回答4:
Some thing like this.
import React from \'react\';
import {
BrowserRouter as Router, Route, NavLink, Switch, Link
} from \'react-router-dom\';
import \'../assets/styles/App.css\';
const Home = () =>
<NormalNavLinks>
<h1>HOME</h1>
</NormalNavLinks>;
const About = () =>
<NormalNavLinks>
<h1>About</h1>
</NormalNavLinks>;
const Help = () =>
<NormalNavLinks>
<h1>Help</h1>
</NormalNavLinks>;
const AdminHome = () =>
<AdminNavLinks>
<h1>root</h1>
</AdminNavLinks>;
const AdminAbout = () =>
<AdminNavLinks>
<h1>Admin about</h1>
</AdminNavLinks>;
const AdminHelp = () =>
<AdminNavLinks>
<h1>Admin Help</h1>
</AdminNavLinks>;
const AdminNavLinks = (props) => (
<div>
<h2>Admin Menu</h2>
<NavLink exact to=\"/admin\">Admin Home</NavLink>
<NavLink to=\"/admin/help\">Admin Help</NavLink>
<NavLink to=\"/admin/about\">Admin About</NavLink>
<Link to=\"/\">Home</Link>
{props.children}
</div>
);
const NormalNavLinks = (props) => (
<div>
<h2>Normal Menu</h2>
<NavLink exact to=\"/\">Home</NavLink>
<NavLink to=\"/help\">Help</NavLink>
<NavLink to=\"/about\">About</NavLink>
<Link to=\"/admin\">Admin</Link>
{props.children}
</div>
);
const App = () => (
<Router>
<div>
<Switch>
<Route exact path=\"/\" component={Home}/>
<Route path=\"/help\" component={Help}/>
<Route path=\"/about\" component={About}/>
<Route exact path=\"/admin\" component={AdminHome}/>
<Route path=\"/admin/help\" component={AdminHelp}/>
<Route path=\"/admin/about\" component={AdminAbout}/>
</Switch>
</div>
</Router>
);
export default App;
回答5:
You can try something like
Routes.js
import React, { Component } from \'react\'
import { BrowserRouter as Router, Route } from \'react-router-dom\';
import FrontPage from \'./FrontPage\';
import Dashboard from \'./Dashboard\';
import AboutPage from \'./AboutPage\';
import Backend from \'./Backend\';
import Homepage from \'./Homepage\';
import UserPage from \'./UserPage\';
class Routes extends Component {
render() {
return (
<div>
<Route exact path=\"/\" component={FrontPage} />
<Route exact path=\"/home\" component={Homepage} />
<Route exact path=\"/about\" component={AboutPage} />
<Route exact path=\"/admin\" component={Backend} />
<Route exact path=\"/admin/home\" component={Dashboard} />
<Route exact path=\"/users\" component={UserPage} />
</div>
)
}
}
export default Routes
App.js
import React, { Component } from \'react\';
import logo from \'./logo.svg\';
import \'./App.css\';
import { BrowserRouter as Router, Route } from \'react-router-dom\'
import Routes from \'./Routes\';
class App extends Component {
render() {
return (
<div className=\"App\">
<Router>
<Routes/>
</Router>
</div>
);
}
}
export default App;
I think you can achieve the same from here also.
回答6:
Here I\'ve created an example in TSX, if someone wants to get rid of prefix of wrapper route in child route path: https://stackoverflow.com/a/47891060/5517306
回答7:
interface IDefaultLayoutProps {
children: React.ReactNode
}
const DefaultLayout: React.SFC<IDefaultLayoutProps> = ({children}) => {
return (
<div className=\"DefaultLayout\">
{children}
</div>
);
}
const LayoutRoute: React.SFC<IDefaultLayoutRouteProps & RouteProps> = ({component: Component, layout: Layout, ...rest}) => {
const handleRender = (matchProps: RouteComponentProps<{}, StaticContext>) => (
<Layout>
<Component {...matchProps} />
</Layout>
);
return (
<Route {...rest} render={handleRender}/>
);
}
const ScreenRouter = () => (
<BrowserRouter>
<div>
<Link to=\"/\">Home</Link>
<Link to=\"/counter\">Counter</Link>
<Switch>
<LayoutRoute path=\"/\" exact={true} layout={DefaultLayout} component={HomeScreen} />
<LayoutRoute path=\"/counter\" layout={DashboardLayout} component={CounterScreen} />
</Switch>
</div>
</BrowserRouter>
);