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 помогут вам лучше понять, как должен работать маршрутизатор.
обзор
Этот урок разделен на несколько разделов. Сначала мы настроим React и React Router, используя npm. Тогда мы перейдем прямо к основам React Router. Вы найдете различные демонстрации кода 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>
- как создать минимальный роутер для навигации и вложенных маршрутов
- как строить динамические маршруты с параметрами пути
Наконец, мы изучили некоторые продвинутые методы маршрутизации для создания финальной демонстрации для защищенных маршрутов.