# Библиотека React-router-dom, кастомные хуки
## План
- Маршрутизация в реакт;
- Устанавливаем React-Router-Dom;
- Использование маршрутизации на практике;
- Разбираем процесс создания собственных хуков;
Зачем нужен SPA?
Какие виды сайтов ещё бывают?
Можно ли делать многостраничные сайты с использованием React?
Принцип маршрутизации
Объект location
Hash History
HTML5 History
React Router DOM
Установка
- Обзор возможностей
- Роутеры
+ HashRouter
+ BrowserRouter
+ MemoryRouter
- Route
- Link/NavLink
Адаптируем SPA под многостраничный сайт
Создание собственных хуков
## Что такое SPA?
Это обычный сайт, на котором всё происходит в рамках одной страницы (Single Page Application) без перезагрузки.
Любой многостраничный сайт можно превратить в одностраничный.
## Принцип маршрутизации
Многостраничный сайт на backend или SPA на Frontend это:
1. Контейнер - основной блок, содержимое в котором отличает одну страницу от другой
2. Содержимое самих страниц
3. Роутер/маршрутизатор - объект, который отслеживает изменение адресной строки и в зависимости от этого размещает в контейнере нужное содержимое страницы
4. Правила маршрутизации - помогают роутеру выбрать правильную страницу для отображения
## Объект location
Как и любой узел, представляющий тег <a/>, объект location содержит свойства:
- hash
- host
- hostname
- href
- origin
- pathname
- port
- protocol
- search
Каждое из этих свойств представляет свою часть текущего адреса страницы
Также объект имеет методы:
- assign() - меняет текущий адрес на заданный
- reload() - обновляет страницу
- replace() - меняет текущий адрес без сохранения записи в истории посещений
## Hash History
Любое изменение якоря (после #) в адресной строке можно отследить с помощью события *hashchange*
```javascript
window.addEventListener('hashchange', e => {
// const [, hash] = e.newURL.split('#');
const { hash } = location;
console.log(hash);
});
```
## HTML5 History
В браузере есть специальный объект history, с помощью которого можно управлять историей посещений в рамках вкладки.
Таким образом, можно добавить посещённые страницы, сделав активной стрелки навигации по истории (вперед/назад)
Пример работы с постраничной навигацией. HTML:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.link {
display: inline-block;
padding: 10px;
}
</style>
</head>
<body>
<div id="app"></div>
<script src="./history.js"></script>
</body>
</html>
```
```javasctipt
window.addEventListener('popstate', e => {
const { state } = e;
console.log(state);
});
document.addEventListener('click', e => {
const link = e.target.closest('.link');
if (!link) {
return;
}
e.preventDefault();
const page = link.textContent;
history.pushState({ page }, '', link.pathname)
});
class Pagination {
constructor(container) {
this.container = container;
}
render() {
for (let i = 1; i < 10; i++) {
const link = document.createElement('a');
link.href = '/page' + i;
link.classList.add('link');
link.textContent = i;
this.container.appendChild(link);
}
}
}
const pagination = new Pagination(document.querySelector('#app'));
pagination.render();
```
## React Router DOM
Осуществляет маршрутизацию React-компонентов на основе изменений адресной строки, истории. Также позволяет реализовать свой собственный принцип маршрутизации
## Установка
npm
```sh
npm install --save react-router-dom
```
yarn
```sh
yarn add react-router-dom
```
## Роутеры
- HashRouter - отслеживает изменения после # в адресной строке
- BrowserRouter - отслеживает изменения в адресной строке целиком. Требует backend части в случае, если адрес будет отличаться от главной
- MemoryRouter - позволяет самостоятельно реализовать свой принцип маршрутизации на основе хранения записей в оперативной памяти
Каждый из роутеров создаётся с помощью функций:
- createBrowserRouter
- createHashRouter
- createMemoryRouter
## Настройка
Осуществляется при создании экземпляра роутера, например через createBrowserRouter
В createRoutesFromElements передаётся структура из маршрутов, по которой роутер будет определять, какой из компонентов необходимо загрузить при изменении адреса
```js
import {
createBrowserRouter,
createRoutesFromElements,
RouterProvider,
Route
} from 'react-router-dom';
```
```jsx
const router = createBrowserRouter(
createRoutesFromElements(
<Route path="/">
<Route path="/" index={true} element={<IndexPage/>}/>
<Route path="/pokemon/:name" element={<PokemonPage/>} loader={pokemonLoader}/>
</Route>
)
);
```
Далее экземпляр подключается к провайдеру RouterProvider в index.js
```jsx
root.render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
```
### Route
Основные props:
- loader - функция-загрузчик компонента. Страница отобразится только после выполнения работы загрузчика. Должна возвращать Promise
- element - компонент, который отобразится после загрузки в контейнер. Без использования компнента <Outlet/> данные загрузятся в корневой элемент приложения
- path - адрес страницы
### Link/NavLink
Link - обычная ссылка, включающая работу роутера
NavLink - ссылка с состоянием. Может добавлять или убирать CSS-класс в зависимости от того, активна ли ссылка (адрес ссылки совпадает с адресной строкой)
## loader
С его помощью можно вынести логику загрузку данных с backend отдельно
````jsx
export const pokemonLoader = async ({ params }) => {
const { name } = params;
const url = 'https://pokeapi.co/api/v2/pokemon/' + name;
const data = await fetch(url);
return await data.json();
}
```
## Получение данных из загрузчика
Для того, чтобы забрать данные из loader'а, достаточно использовать хук useLoaderData:
```jsx
import { useLoaderData, Link } from "react-router-dom"
import { Title } from ".."
export const PokemonPage = () => {
const data = useLoaderData();
const { name, sprites } = data;
const url = sprites.front_default;
return (
<>
<div className="container">
<Link to="/">Main</Link>
<Title className="app-title">{name}</Title>
<img src={url} alt={name} />
</div>
</>
)
}
```
## Произвольная область отрисовки
Для того, чтобы задать участок страницы, в который роутер будет загружать данные страниц, можно использовать компонент <Outlet/>
index.js:
```jsx
const router = createBrowserRouter(
createRoutesFromElements(
<Route
path="/"
element={<MainTemplate />}
loader={mainTemplateLoader}
>
<Route path="/" index loader={homePageLoader} element={<HomePage />} />
<Route path="/types/:type" element={<CardTypePage />} loader={cardTypeLoader}/>
</Route>
)
);
```
MainTemplate:
```jsx
import { useState } from "react";
import { Outlet, useLoaderData } from "react-router-dom";
import { Header } from "../../organisms/Header/Header";
export async function loader() {
const url = 'https://api.magicthegathering.io/v1/types';
const response = await fetch(url);
const json = await response.json();
return json.types;
};
export const MainTemplate = () => {
const types = useLoaderData();
return <>
<Header types={types}/>
<Outlet />
</>
};
```