React Router v4 – полностью переписанная версия распространенного пакета для React. В прошлых версиях React Router использовались такие конфигурации и псевдокомпоненты, что его было тяжело понять. В v4 же содержатся только компоненты.
В этом уроке мы будем создавать веб-сайт (single page application) для местной спортивной команды. Мы разберем основы, необходимые для запуска веб-сайта и роутинга. В статье будут описаны следующие пункты:
- Выбор роутера;
- Создание маршрутов;
- Навигация по маршрутам с помощью ссылок.
Код
Хотите просто посмотреть на то, как работает веб-сайт? Вот. Никакого замысловатого дизайна, только простой и функциональный сайт.
Установка
React Router разделен на 3 пакета: react-router
, react-router-native
и react-router-dom
.
Вам практически никогда не придется устанавливать react-router
напрямую. Этот пакет содержит основные компоненты для маршрутизации и функции для созданных в React Router приложений. Остальные пакеты предоставляют специфические для среды (браузер, React Native) компоненты и заново экспортируют все экспорты react-router
.
Мы создаем веб-сайт (то, что будет запускаться в браузерах), так что нам нужно будет установить react-router-dom
:
npm install --save react-router-dom
Router
При запуске нового проекта необходимо определить, какой тип роутера нужно использовать. Для проектов на основе браузера есть компоненты <BrowserRouter>
и <HashRouter>
. <BrowserRouter>
следует использовать, когда у вас есть сервер, который будет обрабатывать динамические запросы (т. е. знает, как реагировать на любой возможный URI), тогда как <HashRouter>
стоит применять для статических веб-сайтов (где сервер может отвечать только на запросы файлов, о которых он знает).
Обычно предпочтительнее использовать <BrowserRouter>
. Если ваш веб-сайт будет размещен на сервере, который обслуживает только статические файлы, то <HashRouter>
- хороший вариант.
Предположим, что в нашем проекте веб-сайт будет поддерживаться динамическим сервером, поэтому выберем компонент роутера <BrowserRouter>
.
History
Каждый роутер создает объект history, который он использует для отслеживания текущего местоположения и повторного рендеринга веб-сайта при изменении местоположения. Другие компоненты, предоставляемые React Router, опираются на то, что объект history доступен через контекст React. Они должны отображаться как потомки компонента роутера. Компонент роутера React, предком которого не является сам роутер, не сможет работать.
Обработка <Router>
Компоненты роутера ожидают получить только один дочерний элемент. Чтобы работать в рамках этого ограничения, стоит создать компонент <App>
, который отображает остальную часть приложения. Отделение приложения от маршрутизатора также полезно для рендеринга сервера, поскольку вы можете повторно использовать <App>
на сервере при переключении роутера на <MemoryRouter>
.
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render((
<BrowserRouter>
<App />
</BrowserRouter>
), document.getElementById('root'));
Так как мы выбрали роутер, можно начинать обработку single page application, т. е. самого сайта.
<App>
Приложение определяется в компоненте <App>
. Чтобы упростить ситуацию, мы разделим наше приложение на 2 части. Компонент <Header>
будет содержать ссылки для навигации по сайту. Компонент <Main>
- то, где будет отображаться остальная часть контента.
// this component will be rendered by our <___Router>
function App() {
return (
<div>
<Header />
<Main />
</div>
);
}
Макет приложения может каким угодно, но разделение маршрутов и навигации упрощает демонстрацию работы роутера React.
Сначала мы определим контент в компоненте <Main>
. Здесь пройдет обработка маршрутов.
Routes
Компонент <Route>
является основным блоком роутера React. Если вы хотите обрабатывать только контент, основанный на полном имени ссылки, нужно использовать элемент <Route>
.
Path
<Route>
ожидает свойство path. Оно представляет собой строку, описывающую имя пути, которому соответствует маршрут. Например, <Route path='/roster'/>
должен соответствовать пути, начинающемуся с /roster
. Когда путь к текущему местоположению совпадает с path, маршрут будет производить рендер элемента React. Если path не совпадает, маршрут не будет ничего отображать.
<Route path='/roster'/>
// when the pathname is '/', the path does not match
// when the pathname is '/roster' or '/roster/2', the path matches
// If you only want to match '/roster', then you need to use
// the "exact" prop. The following will match '/roster', but not
// '/roster/2'.
<Route exact path='/roster'/>
// You might find yourself adding the exact prop to most routes.
// In the future (i.e. v5), the exact prop will likely be true by
// default. For more information on that, you can check out this
// GitHub issue:
// https://github.com/ReactTraining/react-router/issues/4958
Что касается подбора маршрутов, роутер React заботит только путь к месту. Это означает, что с учетом URL:
http://www.example.com/my-projects/one?extra=false
Роутер React пытается найти соответствующий маршрут только для /my-projects-one
.
Подбор маршрутов
Роутер React использует пакет path-to-regexp
для определения соответствия path элемента и текущего местоположения. Пакет компилирует строку path в регулярное выражение, которое сопоставляется с путевым именем. Строки path имеют более продвинутые параметры форматирования, чем будет описано здесь.
Когда path маршрута находит совпадение, создается соответствующий объект со следующими свойствами:
- url – совпавшая часть путевого имени;
- path – путь маршрута;
- isExact – path === pathname;
- params – объект, содержащий значения из путевого имени, которые были захвачены path-to-regexp.
Вы можете использовать этот тестер маршрутов, чтобы разобраться с сопоставлением маршрутов и URL.
В настоящее время path маршрута должен быть абсолютным.
Создание маршрутов
<Route>
могут быть созданы в любом месте внутри роутера, но чаще всего обрабатывать их стоит в одном и том же месте. Можно использовать компонент <Switch>
для группировки <Route>
. <Switch>
будет перебирать свои дочерние элементы (маршруты) и отображать только первый, соответствующий текущему пути.
Для этого сайта мы хотим найти соответствия следующим путям:
- / - домашняя страница;
- /roster - список команды;
- /roster:number - профиль игрока с использованием его номера;
- /schedule - график игр команды.
Чтобы найти соответствия путям в нашем приложении, необходимо создать элемент <Route>
с указанием path, которому мы хотим соответствовать.
<Switch>
<Route exact path='/' component={Home}/>
{/* both /roster and /roster/:numberbegin with /roster */}
<Route path='/roster' component={Roster}/>
<Route path='/schedule' component={Schedule}/>
</Switch>
Что обрабатывает <Route>?
У маршрутов есть 3 свойства, которые можно использовать для определения того, что должно быть обработано среди соответствий. Элемент <Route>
должен содержать только 1 свойство.
- component – компонент React. Когда маршрут со свойством component находит соответствие, маршрут вернет новый элемент, тип которого предоставляется компонентом React (он создается с использованием React.createElement);
- render – функция, которая возвращает элемент React. Она будет вызываться при нахождении соответствия. Свойство похоже на component, но полезно для встроенного рендеринга и передачи дополнительных свойств элементу;
- children – функция, которая возвращает элемент React. В отличие от предыдущих 2 свойств, она всегда будет отображаться независимо от того, соответствует ли путь маршрута текущему местоположению.
<Route path='/page' component={Page} />
const extraProps = { color: 'red' }
<Route path='/page' render={(props) => (
<Page {...props} data={extraProps}/>
)}/>
<Route path='/page' children={(props) => (
props.match
? <Page {...props}/>
: <EmptyPage {...props}/>
)}/>
Как правило, следует использовать либо component, либо render. Свойство children иногда может быть полезно. Обычно не стоит что-либо обрабатывать при отсутствии совпадений. У нас нет никаких дополнительных свойств для передачи компонентам, поэтому каждый из наших <Route>
будет использовать свойство component.
Элементу, обработанному <Route>
, будет передан ряд свойств. Это будут объекты match
, location
, history
(тот, что был создан роутером).
<Main>
Раз уж мы разобрались с корневой структурой маршрутов, нам просто нужно обработать их. Для этого приложения вида single page application мы будем отображать <Switch>
и <Route>
внутри компонента <Main>
, который поместит HTML, сгенерированный с помощью соответствующего маршрута, в DOM-узел <main>
.
import { Switch, Route } from 'react-router-dom'
function Main() {
return (
<main>
<Switch>
<Route exact path='/' component={Home}/>
<Route path='/roster' component={Roster}/>
<Route path='/schedule' component={Schedule}/>
</Switch>
</main>
);
}
Маршрут домашней страницы включает в себя свойство exact. Оно используется, чтобы указать, что этот маршрут найдет соответствие только тогда, когда имя пути точно совпадет с местоположением.
Вложенные маршруты
Маршрут профиля игрока /roster/:number
не входит в вышеописанный <Switch>
. Вместо этого он будет обработан компонентом <Roster>
, рендер которого происходит всякий раз, когда путь начинается с /roster
.
В компоненте <Roster>
будет осуществляться обработка 2 путей:
/roster
. Соответствие ему может быть найдено только в том случае, если имя пути точно является /roster
, поэтому мы также должны присвоить этому элементу маршрута свойство exact;
/roster/:number
. Этот маршрут применяет параметр path, чтобы захватить часть имени пути, которая идет после /roster
.
function Roster() {
return (
<Switch>
<Route exact path='/roster' component={FullRoster}/>
<Route path='/roster/:number' component={Player}/>
</Switch>
);
}
Может быть полезно сгруппировать маршруты с общим префиксом в одном компоненте. Это позволяет упростить родительские маршруты и дает возможность отображать содержимое, общее для всех маршрутов с одинаковым префиксом.
Например, <Roster>
может обрабатывать заголовок, который будет отображаться для всех маршрутов, путь которых начинается с /roster
.
function Roster() {
return (
<div>
<h2>This is a roster page!</h2>
<Switch>
<Route exact path='/roster' component={FullRoster}/>
<Route path='/roster/:number' component={Player}/>
</Switch>
</div>
);
}
Параметры пути
Иногда в пути есть переменные, которые хочется использовать. Например, с помощью маршрута профиля игрока можно получить номер игрока. Мы можем сделать это, добавив параметры пути в строку path маршрута.
Часть :number
пути /roster/:number
означает, что часть имени пути, которая следует после /roster/
, будет захвачена и сохранена как match.params.number
. Например, pathname /roster/6
сгенерирует объект параметров:
{ number: '6' } // note that the captured value is a string
Компонент <Player>
может использовать объект props.match.param
для определения данных игрока, которые должны быть обработаны.
// an API that returns a player object
import PlayerAPI from './PlayerAPI'
function Player(props) {
const player = PlayerAPI.get(
parseInt(props.match.params.number, 10)
)
if (!player) {
return <div>Sorry, but the player was not found</div>
}
return (
<div>
<h1>{player.name} (#{player.number})</h1>
<h2>{player.position}</h2>
</div>
);
}
Помимо компонента <Player>
, наш веб-сайт также включает компоненты <FullRoster>
, <Schedule>
и <Home>
.
function FullRoster() {
return (
<div>
<ul>
{
PlayerAPI.all().map(p => (
<li key={p.number}>
<Link to={`/roster/${p.number}`}>{p.name}</Link>
</li>
))
}
</ul>
</div>
);
}
function Schedule() {
return (
<div>
<ul>
<li>6/5 @ Evergreens</li>
<li>6/8 vs Kickers</li>
<li>6/14 @ United</li>
</ul>
</div>
);
}
function Home() {
return (
<div>
<h1>Welcome to the Tornadoes Website!</h1>
</div>
);
}
Ссылки
Наконец, приложение должно получить метод навигации между страницами. Если бы мы создавали ссылки с использованием якорных элементов, нажатие на них приводило бы к перезагрузке всей страницы. Роутер React предоставляет компонент <Link>
для предотвращения таких ситуаций. При щелчке по <Link>
URL-адрес будет обновлен, а отображаемый контент изменится без перезагрузки страницы.
import { Link } from 'react-router-dom'
function Header() {
return (
<header>
<nav>
<ul>
<li><Link to='/'>Home</Link></li>
<li><Link to='/roster'>Roster</Link></li>
<li><Link to='/schedule'>Schedule</Link></li>
</ul>
</nav>
</header>
);
}
<Link>
использует свойство to для описания местоположения, к которому нужно совершить переход. Это может быть либо строка, либо объект местоположения (он должен содержать комбинацию свойств pathname, hash, state и search). Если это будет строка, она будет преобразована в объект местоположения.
<Link to={{ pathname: '/roster/7' }}>Player #7</Link>
На данный момент пути в ссылках обязаны быть абсолютными.
Работающий пример
Если вы не хотите возвращаться к началу статьи, то с демонстрацией этого проекта можно ознакомиться на CodeSandbox.
Маршрутизация закончена!
Надеемся, теперь вы готовы создавать собственный веб-сайт.
Мы рассмотрели наиболее важные компоненты, которые пригодятся при создании веб-сайта (<BrowserRouter>
, <Route>
, <Link>
). И все же существует множество компонентов, которые мы не обсудили, а также свойств рассмотренных компонентов.