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;
Пояснения:
render={() => <h1>About</h1>}
– удобный способ задать инлайново рендер React-компонента.exact
– при значенииtrue
, отработка пути будет лишь в случае точного совпадения с location.pathname.<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;
Пояснения:
- match – объект, содержащий информацию о
том, каким образом
<Route path>
соответствуетURL.match
объекту. 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;
Пояснения:
:a
и:b
– именованные параметры.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;
Пояснения:
- location – представление того состояния путей, которые актуальны для приложения на данный момент.
- 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;
Пояснения:
<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;
Пояснения:
-
<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;
Пояснения:
-
<Prompt>
– модуль диалогового окна при переходе на другой роут. when
– условие появление диалогового окна.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
.
При создании статьи были использованы следующие источники: