问题
I'm trying to render most of my routes as children of an AppShell
component, which contains a navbar. But I want to render my 404 route as a standalone component not wrapped in AppShell
.
It was easy with v2:
<Router>
<Route component={AppShell}>
<Route path="/about" component={About} />
<Route path="/" component={Home} />
</Route>
<Route path="*" component={NotFound} />
</Router>
Everything works as desired:
/
renders<AppShell><Home /></AppShell>
/about
renders<AppShell><About /></AppShell>
/blah
renders<NotFound />
But I can't figure out how to do it with v4:
Right now I'm doing this, but the problem is it renders AppShell
(with no children, but still a navbar):
const Routes = () => (
<div>
<AppShell>
<Match exactly pattern="/" component={Home} />
<Match pattern="/about" component={About} />
</AppShell>
<Miss component={NotFound} />
</div>
)
With this:
/
renders<div><AppShell><Home /></AppShell></div>
(good)/about
renders<div><AppShell><About /></AppShell></div>
(good)/blah
renders<div><AppShell /><NotFound /></div>
(problem -- I want to get rid of the<AppShell />
)
Using an array pattern
works if there's no root route:
const InAppShell = (): React.Element<any> => (
<AppShell>
<Match pattern="/about" component={About} />
<Match pattern="/contact" component={Contact} />
</AppShell>
)
const App = (): React.Element<any> => (
<div>
<Match pattern={['/contact', '/about']} component={InAppShell} />
<Miss component={NotFound} />
</div>
)
And using an array pattern
with exactly
works with the root route:
But then I have to put all possible child routes in the pattern
array...
const InAppShell = (): React.Element<any> => (
<AppShell>
<Match exactly pattern="/" component={Home} />
<Match pattern="/about" component={About} />
</AppShell>
)
const App = (): React.Element<any> => (
<div>
<Match exactly pattern={["/", "/about"]} component={InAppShell} />
<Miss component={NotFound} />
</div>
)
But that would be pretty unwieldly in a large app with a bunch of routes.
I could make a separate Match
for /
:
const InAppShell = (): React.Element<any> => (
<AppShell>
<Match exactly pattern="/" component={Home} />
<Match pattern="/about" component={About} />
<Match pattern="/contact" component={Contact} />
</AppShell>
)
const App = (): React.Element<any> => (
<div>
<Match exactly pattern="/" component={InAppShell} />
<Match pattern={["/about", "/contact"]} component={InAppShell} />
<Miss component={NotFound} />
</div>
)
But this would remount <AppShell>
every time I transition to and from the home route.
There doesn't seem to be an ideal solution here; I think this is a fundamental API design challenge v4 will need to solve.
If only I could do something like <Match exactlyPattern="/" pattern={["/about", "/contact"]} component={InAppShell} />
...
回答1:
I've been working around the same issue - I think you may want something like this, a 'global 404':
https://codepen.io/pshrmn/pen/KWeVrQ
const {
HashRouter,
Route,
Switch,
Link,
Redirect
} = ReactRouterDOM
const Global404 = () => (
<div>
<h1>Oh, no!</h1>
<p>You weren't supposed to see this... it was meant to be a surprise!</p>
</div>
)
const Home = () => (
<div>
The links to "How?" and "Random" have no matching routes, so if you click on either of them, you will get a "global" 404 page.
</div>
)
const Question = ({ q }) => (
<div>
<div>Question: {q}</div>
<div>Answer: I have no idea</div>
</div>
)
const Who = () => <Question q={"Who?"}/>
const What = () => <Question q={"What?"}/>
const Where = () => <Question q={"Where?"}/>
const When = () => <Question q={"When?"}/>
const Why = () => <Question q={"Why?"}/>
const RedirectAs404 = ({ location }) =>
<Redirect to={Object.assign({}, location, { state: { is404: true }})}/>
const Nav = () => (
<nav>
<ul>
<li><Link to='/'>Home</Link></li>
<li><Link to='/faq/who'>Who?</Link></li>
<li><Link to='/faq/what'>What?</Link></li>
<li><Link to='/faq/where'>Where?</Link></li>
<li><Link to='/faq/when'>When?</Link></li>
<li><Link to='/faq/why'>Why?</Link></li>
<li><Link to='/faq/how'>How?</Link></li>
<li><Link to='/random'>Random</Link></li>
</ul>
</nav>
)
const App = () => (
<Switch>
<Route exact path='/' component={Home}/>
<Route path='/faq' component={FAQ}/>
<Route component={RedirectAs404}/>
</Switch>
)
const FAQ = () => (
<div>
<h1>Frequently Asked Questions</h1>
<Switch>
<Route path='/faq/who' component={Who}/>
<Route path='/faq/what' component={What}/>
<Route path='/faq/where' component={Where}/>
<Route path='/faq/when' component={When}/>
<Route path='/faq/why' component={Why}/>
<Route component={RedirectAs404}/>
</Switch>
</div>
)
ReactDOM.render((
<HashRouter>
<div>
<Nav />
<Route render={({ location }) => (
location.state && location.state.is404
? <Global404 />
: <App />
)}/>
</div>
</HashRouter>
), document.getElementById('root'))
回答2:
Probably my least favorite thing about v4 is that matches and misses that should be grouped together can be placed on separate levels in the component tree. This leads to situations like yours where you have a component that should only be rendered for certain matches, but the multi-level match structure allows you to nest matches in it.
You should just render the <AppShell>
as a container for each component that requires it.
const Home = (props) => (
<AppShell>
<div>
<h1>Home</h1>
</div>
</AppShell>
)
const About = (props) => (
<AppShell>
<div>
<h1>About</h1>
</div>
</AppShell>
)
const App = () => (
<div>
<Match exactly pattern='/' component={Home} />
<Match pattern="/about" component={About} />
<Miss component={NotFound} />
</div>
)
You could also use the <MatchRoutes>
component. I prefer this because it forces related routes to be grouped together.
const App = () => (
<MatchRoutes missComponent={NotFound} routes={[
{ pattern: '/', exact: true, component: Home },
{ pattern: '/about', component: About }
]} />
)
回答3:
I came up with a custom <Match>
component that is one possible solution. It's far from being a standard agreed-upon way of doing things though.
https://gist.github.com/jedwards1211/15140b65fbeafcbc14dec728fee16f59
Usage looks like
const InAppShell = (): React.Element<any> => (
<AppShell>
<Match exactPattern="/" component={Home} />
<Match pattern="/about" component={About} />
<Match pattern="/contact" component={Contact} />
</AppShell>
)
const App = (): React.Element<any> => (
<div>
<Match exactPattern="/" patterns={["/about", "/contact"]} register={false} component={InAppShell} />
<Match notExactPattern="/" notPatterns={["/about", "/contact"]} register={false} component={NotFound} />
</div>
)
来源:https://stackoverflow.com/questions/40917527/react-router-v4-how-to-avoid-rendering-parent-routes-when-child-routes-miss