Полное руководство по React

год назад·3 мин. на чтение

Это руководство поможет плавно войти в Реакт разработку и понять как использовать react.

Мы начнем с самых базовых концепций. Рассмотрим различные варианты добавления ReactJS на сайт и создания React проекта. Создадим первые компоненты и шаг за шагом будем подробно рассматривать все части компонента - JSX, события, стили, хуки и т.д. Данный react туториал на русском разделен на удобные части, каждая из которых включает подробное объяснение и простые примеры. Этот гайд подойдет и для начинающих react разработчиков. Также весь API собран в удобный справочник, в котором можно найти подробное объяснение конкретных функций React.

Установка

В этом разделе полного руководства по Реакт собраны все варианты установки React. Можно создать проект на React с нуля или постепенно внедрять его в существующее приложение. Также узнаем как настроить базовый набор инструментов на компьютере для разработки и отладки React приложения.

Интерфейс пользователя

В этой части руководства по React мы напишем первый React компонент. React компоненты - это переиспользуемые элементы пользовательского интерфейса. В приложении React каждая часть пользовательского интерфейса является компонентом.

Добавление интерактивности

Эта часть руководства по React посвящена тому как обрабатывать действия пользователя. Некоторые элементы на экране обновляются в ответ на действия пользователя. Например, клик по галерее изображений переключает активное изображение. В React данные, которые меняются со временем, называются состоянием. Вы можете добавить состояние к любому компоненту и обновить его по мере необходимости. В этой части курса по Реакт вы узнаете, как писать компоненты, которые обрабатывают взаимодействия, обновляют свое состояние и отображают разные выходные данные с течением времени.

Управление состоянием

Данная часть react js руководства включает подробное объяснение работы с состоянием. По мере роста вашего React приложения полезно более внимательно относиться к тому, как организовано состояние и как данные передаются между вашими компонентами. Избыточное или повторяющееся состояние является распространенным источником ошибок. В этой части вы узнаете, как правильно структурировать состояние, как поддерживать логику обновления состояния и как обмениваться состоянием между компонентами.

Продвинутые темы

Некоторым из ваших компонентов может потребоваться управление и синхронизация с системами вне React. Например, вам может потребоваться сфокусировать ввод с помощью API браузера, воспроизвести и приостановить видеопроигрыватель, реализованный без React, или подключиться и прослушать сообщения с удаленного сервера. В этой части курса по Реакт вы узнаете о лозейках, позволяющих «выйти за пределы» React и подключиться к внешним системам. Большая часть логики вашего приложения и потока данных не должны полагаться на эти функции.
  • Доступ к данным через рефы
  • Управление DOM с помощью рефов
  • Синхронизация с помощью эффектов
  • Вам может не понадобиться эффект
  • Жизненный цикл реактивных эффектов
  • Отделение событий от эффектов
  • Удаление лишних зависимостей эффектов
  • Переиспользование логики с кастомными хуками

Полное руководство по React Router v6. Часть 2 - Продвинутые определения маршрутов

год назад·8 мин. на чтение

В этой статье рассмотрим то, как описывать роуты в React Router версии 6. Как создавать вложенные маршруты, контекст, хук useRoutes и многое другое.

Серия статей о React Router v6 состоит из 4 частей.
  1. Основы React Router
  2. Продвинутые определения маршрутов (рассматривается в этой статье)
  3. Управление навигацией
  4. Подробно о роутерах
Здесь React Router действительно становится интересным. Есть много интересных вещей, которые вы можете сделать с роутингом, чтобы сделать более сложные маршруты, более удобные для чтения и в целом более функциональные. Это можно сделать с помощью пяти основных методов.
  1. Динамическая маршрутизация
  2. Приоритет маршрутизации
  3. Вложенные маршруты
  4. Множественные маршруты
  5. Хук useRoutes

Динамическая маршрутизация

Самая простая и распространенная расширенная функция в React Router — это обработка динамических маршрутов. В нашем примере предположим, что мы хотим визуализировать компонент для отдельных книг в нашем приложении. Мы могли бы жестко закодировать каждый из этих маршрутов, но если у нас есть сотни книг или возможность для пользователей создавать книги, то невозможно жестко закодировать все эти маршруты. Вместо этого нам нужен динамический маршрут.
<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/books" element={<BookList />} />
  <Route path="/books/:id" element={<Book />} />
</Routes>
Последний маршрут в приведенном выше примере — это динамический маршрут с динамическим параметром :id. Для определения динамического маршрута в React Router нужно поставить двоеточие перед тем, что должно изменяться. В нашем случае наш динамический маршрут будет соответствовать любому URL-адресу, который начинается с /book и заканчивается каким-либо значением. Например, /books/1, /books/bookName и /books/literally-anything будут соответствовать нашему динамическому маршруту. Почти всегда, когда у вас есть такой динамический маршрут, вы хотите получить доступ к динамическому значению в вашем пользовательском компоненте, где на помощь приходит хук useParams.
import { useParams } from "react-router-dom"

export function Book() {
  const { id } = useParams()

  return (
    <h1>Book {id}</h1>
  )
}
Хук useParams не принимает параметры и возвращает объект с ключами, соответствующими динамическим параметрам в вашем маршруте. В нашем случае нашим динамическим параметром является :id, поэтому хук useParams вернет объект, имеющий ключ id, и значение этого ключа будет фактическим идентификатором в нашем URL. Например, если бы наш URL-адрес был /books/3, наша страница отображала бы Book 3.

Приоритет маршрутизации

Когда мы имели дело только с жестко закодированными маршрутами, было довольно легко узнать, какой маршрут будет отображаться, но при работе с динамическими маршрутами это может быть немного сложнее. Возьмем, к примеру, эти маршруты.
<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/books" element={<BookList />} />
  <Route path="/books/:id" element={<Book />} />
  <Route path="/books/new" element={<NewBook />} />
</Routes>
Если у нас есть URL-адрес /books/new, какой маршрут будет задействован? Технически у нас есть два маршрута, которые совпадают. И /books/:id, и /books/new. Они будут совпадать, так как динамический маршрут будет просто предполагать, что new является частью :id URL-адреса, поэтому React Router нужен другой способ определить /books/:id какой маршрут отренедрить. В старых версиях React Router маршрут, который был определен первым, будет визуализирован, поэтому в нашем случае будет отображаться маршрут /books/:id, что, очевидно, не то, что нам нужно. К счастью, версия 6 React Router изменила это подход, поэтому теперь React Router будет использовать алгоритм, чтобы определить, какой маршрут, скорее всего, является тем, который вам нужен. В нашем случае мы, очевидно, хотим отобразить /books/new, поэтому React Router выберет этот маршрут для нас. Фактический принцип работы этого алгоритма очень похож на специфику CSS, поскольку он попытается определить, какой маршрут, соответствующий нашему URL-адресу, является наиболее конкретным (имеет наименьшее количество динамических элементов), и выберет этот маршрут. Раз уж мы заговорили о приоритете маршрутизации, я также хочу поговорить о том, как создать маршрут, который соответствует всему.
<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/books" element={<BookList />} />
  <Route path="/books/:id" element={<Book />} />
  <Route path="/books/new" element={<NewBook />} />
  <Route path="*" element={<NotFound />} />
</Routes>
* соответствует чему угодно, что делает его идеальным для таких вещей, как страница 404. Маршрут, содержащий *, также будет менее конкретным, чем все остальное, поэтому вы никогда случайно не сопоставите маршрут *, когда другой маршрут также совпал бы.

Вложенные маршруты

В приведенном выше примере у нас есть три маршрута, которые начинаются с /books, поэтому мы можем вложить эти маршруты друг в друга, чтобы очистить наши маршруты.
<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/books">
    <Route index element={<BookList />} />
    <Route path=":id" element={<Book />} />
    <Route path="new" element={<NewBook />} />
  </Route>
  <Route path="*" element={<NotFound />} />
</Routes>
Это вложение довольно просто сделать. Все, что вам нужно сделать, это создать родительский Route, в котором свойство path будет установлено на общий путь для всех ваших дочерних компонентов Route. Внутри родительского маршрута вы можете поместить все дочерние компоненты Route. Единственное отличие состоит в том, что проп path дочерних компонентов Route больше не включает общий маршрут /books. Кроме того, маршрут для /books заменяется компонентом Route, который не имеет проп path, но вместо этого имеет index. Все это говорит о том, что путь индексного маршрута совпадает с родительским Route. Но истинная сила вложенных маршрутов заключается в том, как они обрабатывают общие макеты.

Общие макеты (layout)

Давайте представим, что мы хотим отобразить раздел nav со ссылками на каждую книгу, а также форму для добавления книг с любой из наших страниц. Чтобы сделать это, как правило, нам нужно было бы создать общий компонент для хранения этой навигации, а затем импортировать его в каждый отдельный компонент, связанный с книгой. Однако это создает дублирование, поэтому React Router создал собственный вариант решения этой проблемы. Если вы передадите проп element родительскому маршруту, он отобразит этот компонент для каждого дочернего Route, что означает, что вы можете легко разместить общую навигацию или другие общие компоненты на каждой дочерней странице.
<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/books" element={<BooksLayout />}>
    <Route index element={<BookList />} />
    <Route path=":id" element={<Book />} />
    <Route path="new" element={<NewBook />} />
  </Route>
  <Route path="*" element={<NotFound />} />
</Routes>
import { Link, Outlet } from "react-router-dom"

export function BooksLayout() {
  return (
    <>
      <nav>
        <ul>
          <li><Link to="/books/1">Book 1</Link></li>
          <li><Link to="/books/2">Book 2</Link></li>
          <li><Link to="/books/new">New Book</Link></li>
        </ul>
      </nav>

      <Outlet />
    </>
  )
}
Наш новый код будет работать следующим образом: всякий раз, когда мы сопоставляем маршрут внутри родительского Route /book, он будет отображать компонент BooksLayout, который содержит нашу общую навигацию. Route, какой бы дочерний маршрут ни был сопоставлен, он будет отображаться везде, т.к. компонент Outlet находится внутри нашего компонента макета. Компонент Outlet — это, по сути, компонент-заполнитель, который будет отображать любое содержимое нашей текущей страницы. Эта структура невероятно полезна и делает обмен кодом между маршрутами невероятно простым. Теперь последний способ, которым вы можете поделиться макетами с React Router, - это обернуть дочерние компоненты Route в родительский Route, который определяет только проп element и не имеет пропса path.
<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/books" element={<BooksLayout />}>
    <Route index element={<BookList />} />
    <Route path=":id" element={<Book />} />
    <Route path="new" element={<NewBook />} />
  </Route>
  <Route element={<OtherLayout />}>
    <Route path="/contact" element={<Contact />} />
    <Route path="/about" element={<About />} />
  </Route>
  <Route path="*" element={<NotFound />} />
</Routes>
Этот фрагмент кода создаст два маршрута, /contact и /about, которые отображаются внутри компонента OtherLayout. Метод оборачивания нескольких компонентов Route в родительский компонент Route без пропса path полезен, если вы хотите, чтобы эти маршруты использовали один макет, даже если у них нет похожего path.

Контекст Outlet

Еще одна важная вещь, которую нужно знать о компонентах Outlet, это то, что они могут принимать проп context, который будет работать так же, как контекст React.
import { Link, Outlet } from "react-router-dom"

export function BooksLayout() {
  return (
    <>
      <nav>
        <ul>
          <li><Link to="/books/1">Book 1</Link></li>
          <li><Link to="/books/2">Book 2</Link></li>
          <li><Link to="/books/new">New Book</Link></li>
        </ul>
      </nav>

      <Outlet context={{ hello: "world" }} />
    </>
  )
}
import { useParams, useOutletContext } from "react-router-dom"

export function Book() {
  const { id } = useParams()
  const context = useOutletContext()

  return (
    <h1>Book {id} {context.hello}</h1>
  )
}
Как видно из этого примера, мы передаем значение контекста { hello: "world" }, а затем в нашем дочернем компоненте мы используем хук useOutletContext для доступа к значению нашего контекста. Это довольно распространенный шаблон для использования, поскольку часто у вас будут общие данные между всеми вашими дочерними компонентами, что является идеальным вариантом использования для этого контекста.

Множественные маршруты

Еще одна невероятно мощная вещь, которую вы можете сделать с помощью React Router, — это использовать несколько компонентов Routes одновременно. Это может быть сделано либо в виде двух отдельных компонентов Routes, либо в виде вложенных Routes.

Отдельные Routes

Если вы хотите отобразить два разных раздела контента, которые зависят от URL-адреса приложения, вам потребуется несколько компонентов Routes. Это очень распространенный случай, если, например, у вас есть боковая панель, на которой вы хотите отображать определенный контент для определенных URL-адресов, а также главная страница, которая должна отображать определенный контент на основе URL-адреса.
import { Route, Routes, Link } from "react-router-dom"
import { Home } from "./Home"
import { BookList } from "./BookList"
import { BookSidebar } from "./BookSidebar"

export function App() {
  return (
    <>
      <nav>
        <ul>
          <li><Link to="/">Home</Link></li>
          <li><Link to="/books">Books</Link></li>
        </ul>
      </nav>

      <aside>
        <Routes>
          <Route path="/books" element={<BookSidebar />}>
        </Routes>
      </aside>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/books" element={<BookList />} />
      </Routes>
    </>
  )
}
В приведенном выше примере у нас есть два Routes. Основные маршруты Routes определяют все основные компоненты для нашей страницы, а затем у нас есть вторичные Routes внутри aside, которые будут отображать боковую панель для страницы наших книг, когда мы находимся в /books. Это означает, что если наш URL-адрес /books, то оба наших компонента Routes будут отображать контент, поскольку они оба имеют уникальное совпадение для /books в своих Routes. Еще одна вещь, которую вы можете сделать с несколькими компонентами Routes, — это жестко закодировать проп location.
<Routes location="/books">
  <Route path="/books" element={<BookSidebar />}>
</Routes>
Жестко кодируя проп location, мы переопределяем поведение по умолчанию React Router, поэтому независимо от того, каков URL-адрес нашей страницы, этот компонент Routes будет соответствовать своему Route, как если бы URL-адрес был /books.

Вложенные Routes

Другой способ использования нескольких компонентов Routes заключается в том, чтобы вложить их друг в друга. Это довольно распространено, если у вас много маршрутов и вы хотите очистить свой код, переместив похожие маршруты в свои собственные файлы.
<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/books/*" element={<BookRoutes />} />
  <Route path="*" element={<NotFound />} />
</Routes>
import { Routes, Route } from "react-router-dom"
import { BookList } from "./pages/BookList"
import { Book } from "./pages/Book"
import { NewBook } from "./pages/NewBook"
import { BookLayout } from "./BookLayout"

export function BookRoutes() {
  return (
    <Routes>
      <Route element={<BookLayout />}>
        <Route index element={<BookList />} />
        <Route path=":id" element={<Book />} />
        <Route path="new" element={<NewBook />} />
        <Route path="*" element={<NotFound />} />
      </Route>
    </Routes>
  )
}
Вложенные Routes в React Router довольно просты в реализации. Все, что вам нужно сделать, это создать новый компонент для хранения вложенных Routes, этот компонент должен иметь компонент Routes, а внутри этого компонента Routes должны быть все компоненты Route, которые вы сопоставляете с родительским Route. В нашем случае мы перемещаем все наши маршруты /books в этот компонент BookRoute. Затем в родительских Routes вам нужно определить Route, который имеет путь, равный пути, разделяемому всеми вашими вложенными Routes. В нашем случае это будет /books. Однако важно то, что вам нужно заканчивать path родительского маршрута маршрутом *, иначе он не будет правильно соответствовать дочерним роутам. По сути, код, который мы написали, говорит, что всякий раз, когда маршрут начинается с /book/, он должен искать внутри компонента BookRoutes, чтобы увидеть, есть ли соответствующий Route. Вот почему у нас есть еще один маршрут * в BookRoutes, чтобы мы могли гарантировать, что если наш URL-адрес не совпадает ни с одним из BookRoutes, он правильно отобразит компонент NotFound.

Хук useRoutes

Последнее, что вам нужно знать об определении маршрутов в React Router, это то, что вы можете использовать объект JavaScript для определения ваших маршрутов вместо JSX.
import { Route, Routes } from "react-router-dom"
import { Home } from "./Home"
import { BookList } from "./BookList"
import { Book } from "./Book"

export function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/books">
        <Route index element={<BookList />} />
        <Route path=":id" element={<Book />} />
      </Route>
    </Routes>
  )
}
import { Route, Routes } from "react-router-dom"
import { Home } from "./Home"
import { BookList } from "./BookList"
import { Book } from "./Book"

export function App() {
  const element = useRoutes([
    {
      path: "/",
      element: <Home />
    },
    {
      path: "/books",
      children: [
        { index: true, element: <BookList /> },
        { path: ":id", element: <Book /> }
      ]
    }
  ])

  return element
}
Эти два компонента имеют одни и те же маршруты, единственное различие заключается в том, как они были определены. Если вы все же решите, что хотите использовать хук useRoutes, все пропсы, которые вы обычно передаете компонентам Route, вместо этого просто передаются в виде пар ключ-значение объекта.