Статьи

React Router v4: полное руководство

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 Router Этот урок разделен на несколько разделов. Сначала мы настроим React и React Router, используя npm. Тогда мы перейдем прямо к основам React Router. Вы найдете различные демонстрации кода React Router в действии. Примеры, описанные в этом руководстве, включают:

  1. базовая навигационная маршрутизация
  2. вложенная маршрутизация
  3. вложенная маршрутизация с параметрами пути
  4. защищенная маршрутизация

Все концепции, связанные со строительством этих маршрутов, будут обсуждаться по пути. Весь код проекта доступен на этом репозитории 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:

  1. <BrowserRouter>
  2. <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>
  • как создать минимальный роутер для навигации и вложенных маршрутов
  • как строить динамические маршруты с параметрами пути

Наконец, мы изучили некоторые продвинутые методы маршрутизации для создания финальной демонстрации для защищенных маршрутов.