Front End

4 jan, 2017

Gerenciando rotas com React Router

Publicidade

Quando começamos a desenvolver um SPA, uma das primeiras coisas que precisamos definir é a forma como controlamos as rotas da aplicação. Em React isso não é diferente. O React Router é uma lib completa para controle de rotas que permite que você consiga configurar as rotas utilizando o formato JSX e também disponibiliza uma API para você configurar diretamente via Javascript.

A lib trabalha com History API, mas também deixa a possibilidade de você trabalhar com Hash(#) se for necessário.

Ela possui features como: rotas aninhadas, lazy loading de componentes, matching para rotas dinâmicas e controle de transição de rotas, além de possuir suporte a server-rendering.

Para utilizá-la, basta instalar utilizando o npm:

npm install react-router --save

No exemplo abaixo, podemos ver como ficaria a estrutura básica para uma aplicação de agenda de contatos. Essa aplicação possuirá duas telas: uma para a listagem dos contatos e outra para a visualização detalhada do contato.

Teremos quatro componentes:

  • App — O componente container da nossa aplicação;
  • ContactList — Lista de contatos;
  • ContactDetail — Componente de visualização de detalhes do contato;
  • NotFound — Página 404.
import React from 'react';
import { render } from 'react-dom';
import { Router, Route, Link, browserHistory } from 'react-router';

const App = React.createClass({
  render() {
    const { children } = this.props;
    return (
      <div>
        {/* you can insert the menu here */}
        {children}
      </div>
    );
  }
});

const contacts = [
  { id: 1, name: 'Jon Snow', email: 'winterishere@jonsnow.com'},
  { id: 2, name: 'Ygritte', email: 'youknownothing@jonsnow.com'},
];

const ContactList = React.createClass({

  render() {
    return (
      <ul>
      {contacts.map(contact => {
        return(
          <li>
            <Link to={`/contacts/${contact.id}`}>
              {contact.name} - {contact.email}
            </Link>
          </li>
        );
      })}
      </ul>
    );
  }
});

const ContactDetail = React.createClass({
  render() {
    const { params } = this.props;

    if(!params.id) return null;

    return (
      <div>
        <h2>Contact Detail</h2>
        <Link to='/contacts'>List all contacts</Link>
      </div>
    );
  }
});

const NotFound = React.createClass({
  render() {
    return (<div>Page Not Found</div>);
  }
});

render((
  <Router history={browserHistory}>
    <Route path='/' component={App}>
      <Route path='contacts' component={ContactList}/>
      <Route path='contacts/:id' component={ContactDetail} />
      <Route path='*' component={NotFound}/>
    </Route>
  </Router>
), document.getElementById('root'));

No exemplo acima, os componentes estão no mesmo arquivo apenas para fins didáticos. É uma boa prática manter apenas uma definição de componente por arquivo.

No bloco abaixo, temos a definição de todas as nossas rotas. Os componentes que utilizamos para essa definição são o Router e o Route.

1-grvqbnggagxltxkowtuvoa

Router

Componente raiz da aplicação. Todas as rotas devem ser definidas dentro dele e deve existir somente um Router por aplicação.

O router espera receber o parâmetro history, que é o objeto que ele utilizará para o controle da transição das páginas. Esse objeto é diretamente importado do react-router, podendo ser o browserHistory ou o hashHistory.

Route

É cada rota da nossa aplicação. Espera como parâmetros as props:

  • path — utiliza para fazer matching com a url;
  • component — componente que será renderizado caso a url dê matching com o path.

O path aceita alguns formatos para definição da rota:

// Rota estática, ou fixa:
path='contacts'
// Rota com parâmetros:
path='contacts/:id'
// Rota com parâmetros opcionais: 
path='contacts(/:id)'

Os parâmetro passados pelas rotas chegam no componente através da propriedade params. No exemplo acima, poderíamos acessar o parâmetro idde dentro do componente respectivo à rota da seguinte forma:

componentDidMount() {
  const id = this.props.params.id;
  ...
}

Outro formato aceito é utilizando o *, que funciona como um coringa:

path=’*’ 
fará matching com todas as rotas
path='contacts/*'
fará matching com todas as rotas a partir de contacts.
ex: 
 contacts/
 contacts/example
 contacts/123

Mais detalhes sobre os formatos suportados podem ser encontrados nesse link da docs.

Um ponto importante a ressaltar é que somente uma rota fará matching por vez, e a prioridade delas vem de cima para baixo. Por esse motivo, conseguimos fazer nossa página 404 apenas utilizando o path=’*’ e colocando ele abaixo de todas as rotas. Caso não seja feito matching com nenhuma rota anterior, ele chega nessa rota e renderiza o component NotFound.

Rotas aninhadas

É comum em nossas aplicações utilizarmos rotas aninhadas. No nosso exemplo acima já estamos utilizando, mas talvez não tenha ficado evidente:

<Route path='/' component={App}>
  <Route path='contacts' component={ContactList} /> 
  ...
</Route>

Nesse trecho, podemos observar que a rota contacts está aninhada a rota /que é nossa rota raiz.

Nossa rota raiz renderiza o componente App, que atualmente funciona apenas como um layout base da nossa aplicação.

Nele poderíamos colocar o rodapé, o menu e componentes que sempre estarão na tela, independentemente da rota em que estivermos.

1-xgvuuvghlgqlj1s2voknhq

Na imagem acima, na linha 7, acessamos a propriedade children, que é inserida pelo react router no nosso componente. Nesse caso, children seria o componente respectivo a uma rota aninhada. Por exemplo:

// URL atual: 'http://app.myapp.com/contacts'
<Route path='/' component={App}>
  <Route path='contacts' component={ContactList} />
  <Route path='contacts/:id' component={ContactDetail} />
</Route>

Dada a URL atual e a nossa configuração de rotas, o children do nosso componente App seria o componente ContactList.

Abaixo segue um layout que com frequência vemos em aplicações:

1-u6i9fxbp9xfomtcyv-xhvw

No layout acima, temos o menu superior e a sidebar que estarão em todas as telas e temos o conteúdo central, que mudará de uma rota para outra.

Nesse caso, poderíamos manter o Menu e a Sidebar no nosso componente App, renderizando o children ao centro.

render () {
  return (
    <div>
      <Menu />
      <Sidebar />
      <section className='content-wrapper'>
        {children}
      </section>
    </div>
  );
}

Em nosso exemplo da agenda, só utilizamos um nível de aninhamento de rota, mas é possível utilizar quantos forem necessários:

<Route path='/' component={App}>
  <Route path='contacts' component={ContactList} /> 
  <Route path='contacts:id' component={ContactDetail}> 
    <Route path='calls' component={ContactCalls} /> 
  </Route>
</Route>

Nesse exemplo acima, temos uma rota aninhada dentro da visualização do contato. Nessa rota iríamos mostrar, abaixo das informações do contato, uma lista de ligações feitas para o mesmo.

Quando a rota calls fizer matching, o componente ContactCalls estará disponível através da propriedade children do componente ContactDetail.

O ContactCall fica como o children de ContactDetail que por sua vez é children de App.

Link

O componente Link é utilizado para fazer a navegação das páginas. esse componente aceita os seguintes parâmetros:

to

O destino para o link, que pode ser passado em dois formatos, string ou objeto.

Em caso de string, deve ser o caminho absoluto para a página, exemplo:

<Link to='/contacts/123'>Contato 123</Link>

Em caso de objeto, pode conter os seguintes parâmetros:

{
  pathname: 'String representando o caminho para o link', 
  query: 'Um objeto com chave valor dos parâmetros da url',
  hash: 'Uma hash para colocar na URL, ex: #uma-hash.',
  state: 'state a ser persistido para o location'
}
  • activeClassName: className a ser inserido no link quando a url do link em questão estiver ativa. É uma forma fácil de estilizar seu link ativo no menu de navegação.
  • activeStyle: Bem parecido com o activeClassName, mas ao invés do className, aceita um objeto de estilos CSS.

Os demais atributos podem ser encontrado nesse link da docs.

Com esses componentes, já é possível construir uma aplicação baseada em rotas. Mas o controle de rotas não para apenas por aqui, há outras questões como rotas dinâmicas e controle de autenticação de usuário que eu pretendo abordar em artigos futuros.

Para se aprofundar no tema, aconselho começar pela documentação que hoje se encontra no Github.