# Библиотека 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 /> </> }; ```