Rotas aninhadas com o roteador de reação v4 / v5

261

Atualmente, estou lutando com rotas de aninhamento usando o roteador de reação v4.

O exemplo mais próximo foi a configuração de rota na documentação do React-Router v4 .

Quero dividir meu aplicativo em duas partes diferentes.

Uma interface e uma área administrativa.

Eu estava pensando em algo assim:

<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} />

O front-end possui um layout e estilo diferentes da área administrativa. Assim, na primeira página, a rota para casa, aproximadamente, e uma deve ser as rotas secundárias.

/ home deve ser renderizado no componente Frontpage e / admin / home deve ser renderizado no componente Back-end.

Tentei algumas variações, mas sempre acabei em não bater / home ou / admin / home.

Editar - 19.04.2017

Como este post tem muitas visualizações no momento, atualizei-o com a solução final. Eu espero que isso ajude alguém.

Edição - 05.05.2017

Soluções antigas removidas

Solução final:

Esta é a solução final que estou usando agora. Este exemplo também possui um componente de erro global, como uma página 404 tradicional.

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;
datoml
fonte
1
Obrigado por atualizar sua pergunta com a resposta final! apenas uma sugestão: talvez você poderia manter apenas a 4ª listagem e o primeiro, já que os outros estão usando versões desatualizadas do api e estão distraindo a partir da resposta
Giuliano Vilela
lol, não tenho idéia do formato dessa data: 08.05.2017 Sugiro que você use o formato universal ISO8601 para datas, se não quiser confundir as pessoas. 08 é o mês ou o dia? ISO8601 = year.month.day hour.minute.second (progressivamente mais granular)
wesm
Boa solução final atualizada, mas acho que você não precisa da previousLocationlógica.
tudorpavel
qual é a motivação para reescrever completamente o roteador de reação. É melhor que seja uma boa razão
Oliver Watkins
É a abordagem declarativa. Assim, você pode configurar seus roteiros como usaria componentes de reação.
precisa saber é

Respostas:

318

No react-router-v4 você não aninha <Routes />. Em vez disso, você os coloca dentro de outro <Component />.


Por exemplo

<Route path='/topics' component={Topics}>
  <Route path='/topics/:topicId' component={Topic} />
</Route>

Deve se tornar

<Route path='/topics' component={Topics} />

com

const Topics = ({ match }) => (
  <div>
    <h2>Topics</h2>
    <Link to={`${match.url}/exampleTopicId`}>
      Example topic
    </Link>
    <Route path={`${match.path}/:topicId`} component={Topic}/>
  </div>
) 

Aqui está um exemplo básico em linha reta do reagem-router documentação .

Lyubomir
fonte
Eu sou capaz de implementar a partir do seu link no exemplo básico, no entanto, quando digito o URL manualmente, ele não funciona no meu servidor localhost. mas faz no seu exemplo. Por outro lado, o HashRouter funciona corretamente quando digito o URL manualmente com #. Você tem alguma idéia de por que, no meu servidor host local, o BrowserRouter não funciona quando digito o URL manualmente?
precisa saber é o seguinte
8
você pode transformar o componente Tópicos em uma classe? e de onde veio o parâmetro 'match'? no render ()?
user1076813
21
Parece ridículo você pode não apenas to="exampleTopicId"com a ${match.url}estar implícito.
Greenimpala 4/17
8
Você pode ter rotas aninhadas de acordo com os documentos reacttraining.com/react-router/web/example/route-config . Isso permitiria a configuração de rota centralizada conforme o tópico nos documentos. Pense em como isso seria insano para gerenciar em um projeto maior, se não estiver disponível.
JustDave
5
Estas não são rotas aninhadas, é um roteamento de nível único que ainda usa a propriedade de renderização de uma Rota que usa um componente funcional como entrada, observe com mais cuidado, não há aninhamento no sentido de roteador de reação <4. RouteWithSubRoutes é um em nível de rotas que usam correspondência de padrões.
precisa
102

react-router v6

Uma atualização para 2020: a próxima versão v6 terá Routecomponentes aninhados que Just Work ™. Veja o código de exemplo nesta postagem do blog .

Enquanto a pergunta original é sobre a v4 / v5 , a resposta certa quando a v6 for enviada será apenas usar isso, se puder . Está atualmente em alfa.


react-router v4 & v5

É verdade que, para aninhar rotas, é necessário colocá-las no componente filho da rota.

No entanto, se você preferir uma sintaxe mais embutida, em vez de dividir suas rotas entre componentes, poderá fornecer um componente funcional ao rendersuporte da rota na qual deseja aninhar.

<BrowserRouter>

  <Route path="/" component={Frontpage} exact />
  <Route path="/home" component={HomePage} />
  <Route path="/about" component={AboutPage} />

  <Route
    path="/admin"
    render={({ match: { url } }) => (
      <>
        <Route path={`${url}/`} component={Backend} exact />
        <Route path={`${url}/home`} component={Dashboard} />
        <Route path={`${url}/users`} component={UserPage} />
      </>
    )}
  />

</BrowserRouter>

Se você está interessado no motivo pelo qual o rendersuporte deve ser usado, e não no componentsuporte, é porque ele impede que o componente funcional inline seja remontado em cada renderização. Veja a documentação para mais detalhes.

Observe que o exemplo agrupa as rotas aninhadas em um fragmento . Antes da reação 16, você pode usar um recipiente <div>.

davnicwil
fonte
16
Graças a Deus a única solução que é meio clara, sustentável e funciona como esperado. Desejo reagir roteador 3 rotas aninhadas estavam de volta.
Merunas Grincalaitis
Este parece perfeito como angular route-outlet
Partha Ranjan
4
Você deveria usar match.path, não match.url. O primeiro é geralmente usado no pathsuporte da rota ; o último quando você empurra uma nova rota (por exemplo, Link toprop)
nbkhope 29/03/19
50

Só queria mencionar que o react-router v4 mudou radicalmente desde que esta pergunta foi publicada / respondida.

Não há mais <Match>componente! <Switch>é garantir que apenas a primeira correspondência seja renderizada. <Redirect>bem .. redireciona para outra rota. Use ou deixe de fora exactpara excluir ou excluir uma correspondência parcial.

Veja os documentos. Eles são ótimos. https://reacttraining.com/react-router/

Aqui está um exemplo que espero que seja útil para responder sua pergunta.

<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>
</Router>

Espero que tenha ajudado, me avise. Se este exemplo não estiver respondendo bem à sua pergunta, diga-me e verei se posso modificá-la.

jar0m1r
fonte
Não há exactem Redirect reacttraining.com/react-router/web/api/Redirect Isso seria muito mais limpo do <Route exact path="/" render={() => <Redirect to="/path" />} />que eu estou fazendo. Pelo menos não vai me deixar usar o TypeScript.
Filuren
2
Entendo corretamente que não existem rotas aninhadas / secundárias (mais)? preciso duplicar a rota base em todas as rotas? O reat-router 4 não fornece nenhuma ajuda para estruturar rotas de maneira sustentável?
Ville
5
@Ville Estou surpreso; você encontrou uma solução melhor? Eu não quero ter rotas em todo o lugar, meu Deus
punkbit
1
isso funcionará, mas verifique se o caminho público está definido como "/" na configuração do webpack para o bundle.js; caso contrário, as rotas aninhadas não funcionarão na atualização da página.
vikrant
6

Algo assim.

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;

Sanjeev Shakya
fonte
6

Eu consegui definir rotas aninhadas envolvendo Switche definindo a rota aninhada antes da rota raiz.

<BrowserRouter>
  <Switch>
    <Route path="/staffs/:id/edit" component={StaffEdit} />
    <Route path="/staffs/:id" component={StaffShow} />
    <Route path="/staffs" component={StaffIndex} />
  </Switch>
</BrowserRouter>

Referência: https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/Switch.md

asukiaaa
fonte
reorganizar o pedido corrigiu meu problema, embora eu não saiba se isso terá efeitos colaterais. mas trabalhar para agora .. obrigado :)
Anbu369
2

Você pode tentar algo como 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;

Eu acho que você pode conseguir o mesmo daqui também.

Aniruddh Agarwal
fonte
bom ponto! Eu pensei da mesma maneira quando comecei com o React após o desenvolvimento do aplicativo de inicialização do Java Spring. a única coisa que eu mudaria é 'div' para 'Switch' no Routes.js. E tbh, você pode definir todas as rotas em app.js mas envolvê-la fora no arquivo index.js por exemplo (criar-reagem-app)
Renascido
Sim você está certo! Eu implementei dessa maneira, é por isso que estou mencionando esse método.
Aniruddh Agarwal
-6
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>
);
EVGENY GLUKHOV
fonte