react-router v4.x.x – заметки по использованию
Тип Поста

react-router v4.x.x – заметки по использованию

react-router v4.x.x – заметки по использованию

Компоненты являются главной частью React и предоставляют мощную, декларативную модель программирования. React Router содержит в себе коллекцию навигационных компонентов, которые можно с легкостью связать с вашим приложением. Хотите ли вы сделать URL для своего приложения, или реализовать удобный способ навигации для React Native, в любом случае, React Router будет работать везде, где есть React-рендер!

В этой статье собраны заметки по работе с React Router для браузерного рендера, а также примеры работы данного роутинга с Redux.

1. Подготовка

Для всех примеров использован стартовый шаблон create-react-app.

Код заметок размещен в репозитории проекта, для удобной навигации, все примеры в репозитории помечены тегами и соответствующие ссылки приведены в статье.

Чтобы начать работу с примерами, можно выполнить клонирование репозитория git@github.com:sbogdanov108/react-router-v4.git, а затем установку необходимы пакетов командой npm i.

Для проверки работоспособность стартового пакета, нужно выполнить команду npm start, после чего должно запуститься в браузере стартовое приложение.

Также предварительно в стартовый шаблон был установлен пакет react-router-dom, который является предметом рассмотрения в данной статье.

Для упрощения процесса разбора принципов работы роутинга, вся работа ведется в файле src/App.js.

1. Пример простого роутинга

import React from 'react';
import {BrowserRouter as Router, Route, Link, NavLink} from 'react-router-dom';

import './App.css';

const Home = () => <h1>Home</h1>;

const isActiveFunc = (match, location) => {
  return match;
};

const Links = () => (
  // Разные способы задания активного стиля
  <nav>
    <NavLink exact activeClassName='active' to='/'>Home</NavLink>

    <NavLink activeStyle={{color: 'green'}} to='/about'>About</NavLink>

    <NavLink isActive={isActiveFunc} activeClassName='active' to='/contact'>Contact</NavLink>

    {/* Другой вариант задания ссылки */}
    {/* <Link to={{pathname: '/about'}}>About</Link> */}
  </nav>
);

const App = () => (
  <Router>
    <div>
      <Links/>

      <Route exact path='/' component={Home}/>
      <Route path='/about' render={() => <h1>About</h1>}/>
      <Route path='/contact' render={() => <h1>Contact</h1>}/>
    </div>
  </Router>
);

export default App;

Пояснения:

  1. render={() => <h1>About</h1>} – удобный способ задать инлайново рендер React-компонента.
  2. exact – при значении true, отработка пути будет лишь в случае точного совпадения с location.pathname.
  3. <NavLink> – специальная версия <Link>, которая позволяет добавлять стилизацию при активном состоянии.

2. Извлечение параметров из адресной строки

import React from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';

const App = () => (
  <Router>
    <div>
      {/* route props destruction */}
      <Route path='/:page?/:subpage?' render={({match}) => (
        <h1>
          PAGE: {match.params.page || 'Home'}<br/> SUBPAGE: {match.params.subpage}
        </h1>
      )} />
    </div>
  </Router>
);

export default App;

Пояснения:

  1. match – объект, содержащий информацию о том, каким образом <Route path> соответствует URL.match объекту.
  2. match.params.page – доступ к параметру адресной строки, имя которого было определено в роуте.

3. Регулярные выражения в роутах

import React from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';

const App = () => (
  <Router>
    <div>
      <Route path='/:a(\d{2}-\d{2}-\d{4}):b(\.[a-z]+)' render={({match}) => (
        <h1>
          param A: {match.params.a} <br/> param B: {match.params.b}
        </h1>
      )}/>
    </div>
  </Router>
);

export default App;

Пояснения:

  1. :a и :b – именованные параметры.
  2. path='/:a(\d{2}-\d{2}-\d{4}):b(\.[a-z]+)' – данное регулярное выражение сработает на URL подобный этому: http://localhost:3000/04-12-2017.html

4. Парсинг параметров запроса

import React from 'react';
import {BrowserRouter as Router, Route, Link} from 'react-router-dom';

import './App.css';

const Links = () => (
  <nav>
    <Link to='/?id=123'>Inline</Link>
    <Link to={{pathname: '/', search: 'id=456'}}>Object</Link>
  </nav>
);

const App = (props) => (
  <Router>
    <div>
      <Links />

      {/* route props destruction */}
      <Route path='/' render={({match, location}) => (
        <div>
          <p>root</p>
          <b>match:</b> <pre>{JSON.stringify(match, null, 2)}</pre>
          <b>location:</b> <pre>{JSON.stringify(location, null, 2)}</pre>
          <b>location.search:</b> <p>{new URLSearchParams(location.search).get('id')}</p>
        </div>
      )}/>
    </div>
  </Router>
);

export default App;

Пояснения:

  1. location – представление того состояния путей, которые актуальны для приложения на данный момент.
  2. URLSearchParams(location.search).get('id') – нативный метод JS для работы с параметрами адресной строки.

5.<Switch> компонент и роут для page not found

import React from 'react';
import {BrowserRouter as Router, Route, Link, Switch} from 'react-router-dom';

import './App.css';

const Links = () => (
  <nav>
    <Link to='/'>Home</Link>
    <Link to='/about'>About</Link>
    <Link to='/contact/xx/zzz/xxxx'>Contact</Link>
  </nav>
);

const App = (props) => (
  <Router>
    <div>
      <Links />

      <Switch>
        <Route exact path='/' render={() => <h1>Home</h1>}/>
        <Route path='/about' render={() => <h1>About</h1>}/>
        <Route render={() => <h1>Page not found</h1>}/>
      </Switch>
    </div>
  </Router>
);

export default App;

Пояснения:

  1. <Switch> – рендерит первый дочерний элемент <Route> или <Redirect>, который совпадет с location. В данном случае, <Route> без path означает, что в него попадут все пути, которые не указаны в других <Route>.

6. Рендер нескольких компонентов для одного роута

import React from 'react';
import {BrowserRouter as Router, Route, Link } from 'react-router-dom';

import './App.css';

const Links = () => (
  <nav>
    <Link to='/'>Home</Link>
    <Link to='/about'>About</Link>
  </nav>
);

// props destruction
const Header = ({match}) => (
  <div className='header'>
    {/* route props destruction */}
    <Route path='/:page' render={({match}) => (
      <h1>{match.params.page} header</h1>
    )}/>
  </div>
);

const Content = ({match}) => (
  <div className='content'>
    <Route path='/:page' render={({match}) => (
      <p>{match.params.page} content</p>
    )}/>
  </div>
);

const App = (props) => (
  <Router>
    <div>
      <Links />
      <Header/>
      <Content/>
    </div>
  </Router>
);

export default App;

В данном случае, для одного роута рендерятся компоненты <Header> и <Content>.

7. Вложенные роуты

import React from 'react';
import {BrowserRouter as Router, Route, Link} from 'react-router-dom';

import './App.css';

const Home = () => <h1>Home</h1>;
const Menu = () => (
  <div>
    <h1>Menu</h1>

    <Link to='/menu/food'>Food</Link>
    <Link to='/menu/drinks'>Drinks</Link>
    <Link to='/menu/sides'>Sides</Link>

    {/* route props destruction */}
    <Route path='/menu/:section' render={({match}) => (
      <h2>{match.params.section}</h2>
    )} />
  </div>
);

const App = (props) => (
  <Router>
    <div>
      <Link to='/'>Home</Link>
      <Link to='/menu'>Menu</Link>

      <Route exact path='/' component={Home} />
      <Route path='/menu' component={Menu} />
    </div>
  </Router>
);

export default App;

В этом примере роут, находящийся в компоненте <Menu>, имеет вложенность относительно этого компонента.

8. Редирект

import React from 'react';
import {BrowserRouter as Router, Route, Link, Redirect} from 'react-router-dom';

import './App.css';

const loggedIn = true;

const Links = () => (
  <nav>
    <Link to='/'>Home</Link>
    <Link to='/old/123'>Old</Link>
    <Link to='/new/456'>New</Link>
    <Link to='/protected'>Protected</Link>
  </nav>
);

const App = (props) => (
  <Router>
    <div>
      <Links />

      <Route exact path='/' render={() => (<h1>Home</h1>)} />

      {/* route props destruction */}
      <Route exact path='/new/:str' render={({match}) => (
        <h1>New: {match.params.str}</h1>
      )} />

      {/* Редирект с /old/123 на /new/456 с передачей old параметра 123 */}
      <Route path='/old/:str' render={({match}) => (
        <Redirect to={`/new/${match.params.str}`} />
      )} />

      {/* Простая защита роута. Происходит редирект, например, при отсутствии залогиненного юзера */}
      <Route path='/protected' render={() => (
        loggedIn
          ? <h1>This is protected page</h1>
          : <Redirect to={'/new/login'} />
      )} />
    </div>
  </Router>
);

export default App;

Пояснения:

  1. <Redirect> – рендер перехода на новую локацию.

9. Подтверждение перехода на другой роутер

import React from 'react';
import {BrowserRouter as Router, Route, Link, Prompt} from 'react-router-dom';

import './App.css';

const Home = () => <h1>Home</h1>;

class Form extends React.Component{
  state = {dirty: false}; // состояние формы - нет изменений

  setDirty = () => this.setState({dirty: true});

  render() {
    return (
      <div>
        <h1>Form</h1>

        <input type='text' onInput={this.setDirty} />

        <Prompt
          when={this.state.dirty}
          message='Данные будут потеряны!' />
      </div>
    )
  }
}

const App = (props) => (
  <Router>
    <div>
      <Link to='/'>Home</Link>
      <Link to='/form'>Form</Link>

      <Route exact path='/' component={Home} />
      <Route path='/form' component={Form} />
    </div>
  </Router>
);

export default App;

Пояснения:

  1. <Prompt> – модуль диалогового окна при переходе на другой роут.
  2. when – условие появление диалогового окна.
  3. message – сообщение для диалогового окна.

10. Разные типы роутов

import React from 'react';
import {BrowserRouter, HashRouter, MemoryRouter, StaticRouter, Route, Link} from 'react-router-dom';

import './App.css';

const LinksRoutes = () => (
  <div>
    <Link to='/'>Home</Link>
    <Link to='/about'>About</Link>

    <Route exact path='/' render={() => <h1>Home</h1>} />
    <Route path='/about' render={() => <h1>About</h1>} />
  </div>
);

// Принудительное обновление страницы
const forceRefresh = () => {
  console.log(new Date());
  return false;
};

const BrowserRouterApp = () => (
  // HTML5 History
  <BrowserRouter forceRefresh={forceRefresh()}>
    <LinksRoutes />
  </BrowserRouter>
);

const HashRouterApp = () => (
  // Hash History
  <HashRouter hashType='slash'>
    <LinksRoutes />
  </HashRouter>
);

const MemoryRouterApp = () => (
  // Memory History - for testing purpose
  <MemoryRouter
    initialEntries={['/', '/about']}
    initialIndex={1}
  >
    <LinksRoutes />
  </MemoryRouter>
);

const StaticRouterApp = () => (
  // Static - for server-side rendering
  <StaticRouter location='/about' context={{}}>
    <LinksRoutes />
  </StaticRouter>
);

// export default StaticRouterApp;
// export default MemoryRouterApp;
export default HashRouterApp;
// export default BrowserRouterApp;

11. React Router & Redux

import React from 'react';
import ReactDOM from 'react-dom';

import { createStore, combineReducers, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';

import createHistory from 'history/createBrowserHistory';
import { Route } from 'react-router';

import { ConnectedRouter, routerReducer, routerMiddleware, push } from 'react-router-redux';

import reducers from './reducers'; // То место, где вы храните редукторы

// Создание выбранной вами истории для браузера
const history = createHistory();

// Создаем middleware для перехвата и отправки действий навигации
const middleware = routerMiddleware(history);

// Добавляем редукторы в хранилище по ключу router
// Применяем наше middleware для навигации
const store = createStore(
  combineReducers({
    ...reducers,
    router: routerReducer
  }),
  applyMiddleware(middleware)
);

// Теперь мы можем отправлять действия навигации из любого места!
// store.dispatch(push('/foo'))

ReactDOM.render(
  <Provider store={store}>
    {/* ConnectedRouter будет использовать store из Provider автоматически */}
    <ConnectedRouter history={history}>
      <div>
        <Route exact path='/' component={Home}/>
        <Route path='/about' component={About}/>
        <Route path='/topics' component={Topics}/>
      </div>
    </ConnectedRouter>
  </Provider>,
  document.getElementById('root')
);

Пример базовых принципов использование роута и пакета react-router-redux.

При создании статьи были использованы следующие источники:

  1. React RouterDocs
  2. React Router Redux Docs
  3. MDN JavaScript Docs
Поделиться

2 комментария

Дмитрий А можно этот роутинг использовать в react native чтобы получать путь и параметры из адресной строки браузера?
Дмитрий в примере с redux нифига не роутер v4 используется, а жаль...

Оставить комментарий

Вы можете использовать следующие HTML-теги:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>
Обязательно к заполнению