React Router — это стандартная библиотека маршрутизации для React. Когда вам нужно перемещаться по приложению React с несколькими представлениями, вам понадобится маршрутизатор для управления URL-адресами. React Router делает это, поддерживая синхронизацию пользовательского интерфейса приложения и URL-адреса.
Это руководство знакомит вас с React Router v4 и тем, что вы можете с ним сделать.
Вступление
React — это популярная библиотека для создания одностраничных приложений (SPA), которые отображаются на стороне клиента. SPA может иметь несколько представлений (или страниц ), и в отличие от обычных многостраничных приложений, перемещение по этим представлениям не должно приводить к перезагрузке всей страницы. Вместо этого мы хотим, чтобы представления были встроены в пределах текущей страницы. Конечный пользователь, привыкший к многостраничным приложениям, ожидает, что в SPA будут присутствовать следующие функции:
- Каждое представление в приложении должно иметь URL, который однозначно определяет это представление. Это сделано для того, чтобы пользователь мог добавить в закладки URL-адрес для последующего использования, например,
www.example.com/products. - Кнопка браузера «назад» и «вперед» должна работать как положено.
- Динамически сгенерированные вложенные представления также должны иметь собственный URL-адрес — например,
example.com/products/shoes/101, где 101 — идентификатор продукта.
Маршрутизация — это процесс синхронизации URL браузера с тем, что отображается на странице. React Router позволяет обрабатывать маршрутизацию декларативно . Подход декларативной маршрутизации позволяет вам контролировать поток данных в вашем приложении, говоря «маршрут должен выглядеть следующим образом»:
<Route path="/about" component={About}/>
Вы можете разместить компонент <Route> любом месте, где вы хотите, чтобы ваш маршрут отображался. Поскольку <Route> , <Link> и все другие API React Router, с которыми мы будем иметь дело, являются лишь компонентами, вы легко можете привыкнуть к маршрутизации в React.
Примечание, прежде чем начать. Существует распространенное заблуждение, что React Router является официальным решением для маршрутизации, разработанным Facebook. На самом деле это сторонняя библиотека, которая широко популярна благодаря своему дизайну и простоте. Если ваши требования ограничены маршрутизаторами для навигации, вы можете реализовать собственный маршрутизатор с нуля без особых хлопот. Однако понимание того, как основы React Router помогут вам лучше понять, как должен работать маршрутизатор.
обзор

- базовая навигационная маршрутизация
- вложенная маршрутизация
- вложенная маршрутизация с параметрами пути
- защищенная маршрутизация
Все концепции, связанные со строительством этих маршрутов, будут обсуждаться по пути. Весь код проекта доступен на этом репозитории GitHub . Как только вы окажетесь в определенном демонстрационном каталоге, запустите npm install для установки зависимостей. Чтобы разместить приложение на сервере разработки, запустите npm start и npm start по http://localhost:3000/ чтобы увидеть демонстрацию в действии.
Давайте начнем!
Настройка React Router
Я предполагаю, что у вас уже есть среда разработки. Если нет, перейдите к разделу « Начало работы с React и JSX ». Кроме того, вы можете использовать Create React App для создания файлов, необходимых для создания базового проекта React. Это структура каталогов по умолчанию, созданная приложением Create React:
react-routing-demo-v4 ├── .gitignore ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json ├── README.md ├── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── index.css │ ├── index.js │ ├── logo.svg │ └── registerServiceWorker.js └── yarn.lock
Библиотека React Router состоит из трех пакетов: react-router , react-router-dom и react-router-native . react-router является базовым пакетом для маршрутизатора, в то время как два других зависят от среды. Вы должны использовать react-router-dom если вы создаете веб-сайт, и react-router-native если вы находитесь в среде разработки мобильных приложений с использованием React Native.
Используйте npm для установки react-router-dom :
npm install --save react-router-dom
Основы React Router
Вот пример того, как будут выглядеть наши маршруты:
<Router> <Route exact path="/" component={Home}/> <Route path="/category" component={Category}/> <Route path="/login" component={Login}/> <Route path="/products" component={Products}/> </Router>
маршрутизатор
Вам нужен компонент маршрутизатора и несколько компонентов маршрута, чтобы настроить базовый маршрут, как показано выше. Поскольку мы создаем приложение на основе браузера, мы можем использовать два типа маршрутизаторов из React Router API:
-
<BrowserRouter> -
<HashRouter>
Основное различие между ними проявляется в URL, которые они создают:
// <BrowserRouter> http://example.com/about // <HashRouter> http://example.com/#/about
<BrowserRouter> более популярен среди двух, потому что он использует API истории HTML5 для отслеживания истории вашего маршрутизатора. <HashRouter> , с другой стороны, использует хеш-часть URL ( window.location.hash ) для запоминания вещей. Если вы намереваетесь поддерживать устаревшие браузеры, вам следует придерживаться <HashRouter> .
Оберните компонент <BrowserRouter> вокруг компонента App.
index.js
/* Import statements */ import React from 'react'; import ReactDOM from 'react-dom'; /* App is the entry point to the React code.*/ import App from './App'; /* import BrowserRouter from 'react-router-dom' */ import { BrowserRouter } from 'react-router-dom'; ReactDOM.render( <BrowserRouter> <App /> </BrowserRouter> , document.getElementById('root'));
Примечание. Компонент маршрутизатора может иметь только один дочерний элемент. Дочерний элемент может быть элементом HTML — например, div — или компонентом реакции.
Чтобы React Router работал, вам нужно импортировать соответствующий API из библиотекиact react-router-dom . Здесь я импортировал BrowserRouter в index.js . Я также импортировал компонент App из App.js App.js , как вы уже догадались, является точкой входа в компоненты React.
Приведенный выше код создает экземпляр истории для всего нашего компонента приложения. Позвольте мне официально познакомить вас с историей.
история
history— это библиотека JavaScript, которая позволяет вам легко управлять историей сеансов везде, где работает JavaScript. history предоставляет минимальный API, который позволяет вам управлять стеком истории, перемещаться, подтверждать навигацию и сохранять состояние между сеансами. — Реагировать на учебные документы
Каждый компонент маршрутизатора создает объект истории, который отслеживает текущее местоположение ( history.location ), а также предыдущие местоположения в стеке. Когда текущее местоположение изменяется, представление повторно визуализируется, и вы получаете ощущение навигации. Как меняется текущее местоположение? У объекта истории есть методы, такие как history.push() и history.replace() чтобы позаботиться об этом. history.push() вызывается при нажатии на компонент <Link> , а history.replace() вызывается при использовании <Redirect> . Другие методы — такие как history.goBack() и history.goForward() — используются для навигации по стеку истории при переходе назад или вперед страницы.
Двигаясь дальше, у нас есть ссылки и маршруты.
Ссылки и маршруты
Компонент <Route> является наиболее важным компонентом в маршрутизаторе React. Он отображает некоторый пользовательский интерфейс, если текущее местоположение соответствует пути маршрута. В идеале, компонент <Route> должен иметь пропущенный path , и если имя пути совпадает с текущим местоположением, он отображается.
Компонент <Link> , с другой стороны, используется для навигации между страницами. Это сопоставимо с элементом привязки HTML. Однако использование якорных ссылок приведет к обновлению браузера, что нам не нужно. Таким образом, вместо этого мы можем использовать <Link> для перехода к определенному URL-адресу и перерисовать представление без обновления браузера.
Мы рассмотрели все, что вам нужно знать для создания базового маршрутизатора. Давайте построим один.
Демо 1: базовая маршрутизация
SRC / App.js
/* Import statements */ import React, { Component } from 'react'; import { Link, Route, Switch } from 'react-router-dom'; /* Home component */ const Home = () => ( <div> <h2>Home</h2> </div> ) /* Category component */ const Category = () => ( <div> <h2>Category</h2> </div> ) /* Products component */ const Products = () => ( <div> <h2>Products</h2> </div> ) /* App component */ class App extends React.Component { render() { return ( <div> <nav className="navbar navbar-light"> <ul className="nav navbar-nav"> /* Link components are used for linking to other views */ <li><Link to="/">Homes</Link></li> <li><Link to="/category">Category</Link></li> <li><Link to="/products">Products</Link></li> </ul> </nav> /* Route components are rendered if the path prop matches the current URL */ <Route path="/" component={Home}/> <Route path="/category" component={Category}/> <Route path="/products" component={Products}/> </div> ) } }
Мы объявили компоненты для дома, категории и продуктов внутри App.js Хотя пока это нормально, когда компонент начинает расти, лучше иметь отдельный файл для каждого компонента. Как правило, я обычно создаю новый файл для компонента, если он занимает более 10 строк кода. Начиная со второй демонстрации, я буду создавать отдельный файл для компонентов, размер которых слишком велик для размещения внутри файла App.js
Внутри компонента App мы написали логику маршрутизации. Путь <Route> совпадает с текущим местоположением, и компонент визуализируется. Компонент, который должен быть визуализирован, передается как вторая опора.
Здесь / соответствует и / и /category . Таким образом, оба маршрута сопоставляются и отображаются. Как мы этого избежать? Вы должны передать exact= {true} реквизиты exact= {true} маршрутизатору с path='/' :
<Route exact={true} path="/" component={Home}/>
Если вы хотите, чтобы маршрут отображался только в том случае, если пути в точности совпадают, вы должны использовать точные реквизиты.
Вложенная маршрутизация
Чтобы создать вложенные маршруты, нам нужно лучше понять, как работает <Route> . Давайте сделаем это.
<Route> есть три реквизита, которые вы можете использовать для определения того, что визуализируется:
- компонент . Мы уже видели это в действии. Когда URL совпадает, маршрутизатор создает элемент React из данного компонента, используя
React.createElement. - визуализация . Это удобно для встроенного рендеринга. Пропеллер рендеринга ожидает функцию, которая возвращает элемент, когда местоположение соответствует пути маршрута.
- дети Прописка children похожа на визуализацию в том, что она ожидает функцию, которая возвращает элемент React. Тем не менее, дочерние элементы визуализируются независимо от того, соответствует ли путь местоположению или нет.
Путь и матч
Путь используется для определения части URL, которой должен соответствовать маршрутизатор. Он использует библиотеку Path-to-RegExp, чтобы превратить строку пути в регулярное выражение. Затем он будет сопоставлен с текущим местоположением.
Если путь и местоположение маршрутизатора успешно сопоставлены, создается объект, и мы называем его объектом сопоставления . Объект соответствия содержит больше информации об URL-адресе и пути. Эта информация доступна через ее свойства, перечисленные ниже:
-
match.url. Строка, которая возвращает совпавшую часть URL. Это особенно полезно для создания вложенных<Link> -
match.pathСтрока, которая возвращает строку пути маршрута, то есть<Route path="">. Мы будем использовать это для создания вложенных<Route>s. -
match.isExact. Логическое значение, которое возвращает true, если совпадение было точным (без конечных символов). -
match.params. Объект, содержащий пары ключ / значение из URL, проанализированного пакетом Path-to-RegExp.
Теперь, когда мы знаем все о <Route> , давайте создадим маршрутизатор с вложенными маршрутами.
Переключатель компонентов
Прежде чем мы приступим к демонстрационному коду, я хочу познакомить вас с компонентом <Switch> . Когда несколько <Route> используются вместе, все подходящие маршруты отображаются включительно. Рассмотрим этот код из демонстрации 1. Я добавил новый маршрут, чтобы продемонстрировать, почему <Switch> полезен.
<Route exact path="/" component={Home}/> <Route path="/products" component={Products}/> <Route path="/category" component={Category}/> <Route path="/:id" render = {()=> (<p> I want this text to show up for all routes other than '/', '/products' and '/category' </p>)}/>
Если URL-адрес /products , отображаются все маршруты, соответствующие местоположению /products . Таким образом, <Route> with path :id отображается вместе с компонентом Products . Это по замыслу. Однако, если это не то поведение, которое вы ожидаете, вы должны добавить компонент <Switch> в ваши маршруты. При использовании <Switch> отображается только первый дочерний <Route> который соответствует расположению.
Демонстрация 2: вложенная маршрутизация
Ранее мы создали маршруты для / , /category и /products . Что делать, если мы хотели URL-адрес формы /category/shoes ?
SRC / App.js
import React, { Component } from 'react'; import { Link, Route, Switch } from 'react-router-dom'; import Category from './Category'; class App extends Component { render() { return ( <div> <nav className="navbar navbar-light"> <ul className="nav navbar-nav"> <li><Link to="/">Homes</Link></li> <li><Link to="/category">Category</Link></li> <li><Link to="/products">Products</Link></li> </ul> </nav> <Switch> <Route exact path="/" component={Home}/> <Route path="/category" component={Category}/> <Route path="/products" component={Products}/> </Switch> </div> ); } } export default App; /* Code for Home and Products component omitted for brevity */
В отличие от более ранней версии React Router, в версии 4 вложенные <Route> должны предпочтительно входить в родительский компонент. То есть компонент Category является здесь родителем, и мы будем объявлять маршруты для category/:name внутри родительского компонента.
SRC / Category.jsx
import React from 'react'; import { Link, Route } from 'react-router-dom'; const Category = ({ match }) => { return( <div> <ul> <li><Link to={`${match.url}/shoes`}>Shoes</Link></li> <li><Link to={`${match.url}/boots`}>Boots</Link></li> <li><Link to={`${match.url}/footwear`}>Footwear</Link></li> </ul> <Route path={`${match.path}/:name`} render= {({match}) =>( <div> <h3> {match.params.name} </h3></div>)}/> </div>) } export default Category;
Во-первых, мы объявили пару ссылок для вложенных маршрутов. Как упоминалось ранее, match.url будет использоваться для создания вложенных ссылок и match.path для вложенных маршрутов. Если у вас возникают проблемы с пониманием концепции соответствия, console.log(match) предоставляет некоторую полезную информацию, которая может помочь прояснить ее.
<Route path={`${match.path}/:name`} render= {({match}) =>( <div> <h3> {match.params.name} </h3></div>)}/>
Это наша первая попытка динамической маршрутизации. Вместо жесткого кодирования маршрутов мы использовали переменную внутри пути. :name является параметром пути и перехватывает все после category/ до следующей косой черты. Таким образом, путь, например products/running-shoes , создаст объект params следующим образом:
{ name: 'running-shoes' }
Захваченные данные должны быть доступны по адресу match.params или props.match.params зависимости от того, как передаются реквизиты. Другая интересная вещь заключается в том, что мы использовали render реквизит. реквизит render очень удобен для встроенных функций, которые не требуют собственного компонента.
Демонстрация 3: Вложенная маршрутизация с параметрами Path
Давайте все усложним немного, не так ли? Реальный маршрутизатор будет иметь дело с данными и отображать их динамически. Предположим, что у нас есть данные о продукте, возвращаемые серверным API в форме ниже.
SRC / Products.jsx
const productData = [ { id: 1, name: 'NIKE Liteforce Blue Sneakers', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin molestie.', status: 'Available' }, { id: 2, name: 'Stylised Flip Flops and Slippers', description: 'Mauris finibus, massa eu tempor volutpat, magna dolor euismod dolor.', status: 'Out of Stock' }, { id: 3, name: 'ADIDAS Adispree Running Shoes', description: 'Maecenas condimentum porttitor auctor. Maecenas viverra fringilla felis, eu pretium.', status: 'Available' }, { id: 4, name: 'ADIDAS Mid Sneakers', description: 'Ut hendrerit venenatis lacus, vel lacinia ipsum fermentum vel. Cras.', status: 'Out of Stock' }, ];
Нам нужно создать маршруты для следующих путей:
-
/products. Это должно отобразить список продуктов. -
/products/:productId. Если продукт с:productIdсуществует, он должен отображать данные продукта, а если нет, то должен отображать сообщение об ошибке.
SRC / Products.jsx
/* Import statements have been left out for code brevity */ const Products = ({ match }) => { const productsData = [ { id: 1, name: 'NIKE Liteforce Blue Sneakers', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin molestie.', status: 'Available' }, //Rest of the data has been left out for code brevity ]; /* Create an array of `<li>` items for each product var linkList = productsData.map( (product) => { return( <li> <Link to={`${match.url}/${product.id}`}> {product.name} </Link> </li> ) }) return( <div> <div> <div> <h3> Products</h3> <ul> {linkList} </ul> </div> </div> <Route path={`${match.url}/:productId`} render={ (props) => <Product data= {productsData} {...props} />}/> <Route exact path={match.url} render={() => ( <div>Please select a product.</div> )} /> </div> ) }
Сначала мы создали список <Links> с использованием productsData.id и сохранили его в linkList . Маршрут принимает параметр в строке пути, который соответствует идентификатору продукта.
<Route path={`${match.url}/:productId`} render={ (props) => <Product data= {productsData} {...props} />}/>
Возможно, вы ожидали component = { Product } вместо встроенной функции рендеринга. Проблема в том, что нам нужно передать productsData компоненту Product вместе со всеми существующими реквизитами. Хотя есть и другие способы сделать это, я считаю этот метод наиболее простым. {...props} props {...props} использует синтаксис расширения ES6 для передачи всего объекта props компоненту.
Вот код для компонента продукта.
SRC / Product.jsx
/* Import statements have been left out for code brevity */ const Product = ({match,data}) => { var product= data.find(p => p.id == match.params.productId); var productData; if(product) productData = <div> <h3> {product.name} </h3> <p>{product.description}</p> <hr/> <h4>{product.status}</h4> </div>; else productData = <h2> Sorry. Product doesnt exist </h2>; return ( <div> <div> {productData} </div> </div> ) }
Метод find используется для поиска в массиве объекта со свойством id, равным match.params.productId . Если продукт существует, отображается productData . Если нет, то выводится сообщение «Товар не существует».
Защита маршрутов
В заключительной демонстрации мы обсудим методы защиты маршрутов. Так что, если кто-то попытается получить доступ к /admin , он должен будет сначала войти в систему. Однако есть некоторые вещи, которые мы должны охватить, прежде чем сможем защитить маршруты.
Перенаправление
Как и перенаправления на стороне сервера, <Redirect> заменит текущее местоположение в стеке истории новым местоположением. Новое местоположение указывается to поддержки. Вот как мы будем использовать <Redirect> :
<Redirect to={{pathname: '/login', state: {from: props.location}}}
Таким образом, если кто-то попытается получить доступ к /admin во время выхода из системы, он будет перенаправлен на маршрут /login . Информация о текущем местоположении передается через состояние, поэтому, если аутентификация прошла успешно, пользователь может быть перенаправлен обратно в исходное местоположение. Внутри дочернего компонента вы можете получить доступ к этой информации по адресу this.props.location.state .
Пользовательские маршруты
Пользовательский маршрут — это модное слово для маршрута, вложенного в компонент. Если нам нужно принять решение о том, следует ли отображать маршрут или нет, написание нестандартного маршрута — это путь. Вот пользовательский маршрут, объявленный среди других маршрутов.
SRC / App.js
/* Add the PrivateRoute component to the existing Routes */ <Switch> <Route exact path="/" component={Home} data={data}/> <Route path="/category" component={Category}/> <Route path="/login" component={Login}/> <PrivateRoute authed={fakeAuth.isAuthenticated} path='/products' component = {Products} /> </Switch>
fakeAuth.isAuthenticated возвращает true, если пользователь вошел в систему, и false в противном случае.
Вот определение для PrivateRoute:
SRC / App.js
/* PrivateRoute component definition */ const PrivateRoute = ({component: Component, authed, ...rest}) => { return ( <Route {...rest} render={(props) => authed === true ? <Component {...props} /> : <Redirect to={{pathname: '/login', state: {from: props.location}}} />} /> ) }
Маршрут отображает компонент Admin, если пользователь вошел в систему. В противном случае пользователь перенаправляется в /login . Преимущество этого подхода в том, что он явно более декларативный, а PrivateRoute можно использовать повторно.
Наконец, вот код для компонента Login:
SRC / Login.jsx
import React from 'react'; import { Redirect } from 'react-router-dom'; class Login extends React.Component { constructor() { super(); this.state = { redirectToReferrer: false } // binding 'this' this.login = this.login.bind(this); } login() { fakeAuth.authenticate(() => { this.setState({ redirectToReferrer: true }) }) } render() { const { from } = this.props.location.state || { from: { pathname: '/' } } const { redirectToReferrer } = this.state; if (redirectToReferrer) { return ( <Redirect to={from} /> ) } return ( <div> <p>You must log in to view the page at {from.pathname}</p> <button onClick={this.login}>Log in</button> </div> ) } } /* A fake authentication function */ export const fakeAuth = { isAuthenticated: false, authenticate(cb) { this.isAuthenticated = true setTimeout(cb, 100) }, }
Строка ниже демонстрирует разрушение объекта , которое является частью спецификации ES6.
const { from } = this.props.location.state || { from: { pathname: '/' } }
Давайте соединим кусочки головоломки вместе, ладно? Вот последняя демонстрация приложения, которое мы создали с использованием маршрутизатора React:
Демонстрация 4: Защита маршрутов
Резюме
Как вы видели в этой статье, React Router — мощная библиотека, которая дополняет React для создания лучших декларативных маршрутов. В отличие от предыдущих версий React Router, в v4 все «просто компоненты». Более того, новый шаблон дизайна идеально вписывается в стиль действий React.
В этом уроке мы узнали:
- как настроить и установить React Router
- основы маршрутизации и некоторые важные компоненты, такие как
<Router>,<Route>и<Link> - как создать минимальный роутер для навигации и вложенных маршрутов
- как строить динамические маршруты с параметрами пути
Наконец, мы изучили некоторые продвинутые методы маршрутизации для создания финальной демонстрации для защищенных маршрутов.