Единственная шпаргалка по ReactJS, которая вам нужна
- четверг, 23 октября 2025 г. в 00:00:07
Всем привет! Меня зовут Анастасия Нечепоренко, я QA Lead и преподаватель курса "JavaScript QA Engineer" в Отус. Добро пожаловать в ещё одну шпаргалку по React! Но подождите, это не то, что вы подумали — не просто набор случайных примеров кода и банальных объяснений, как в других шпаргалках.
Обещаю, это будет по‑настоящему полезно. Мы вместе разберёмся, как работает ReactJS, и как реализованы все его крутые фичи.
Я буду охватывать всё — от самых основ до новейших фич ReactJS 2025 года. Цель этой шпаргалки проста: это должен быть тот единственный документ, который вам нужен для того, чтобы эффективно писать код для следующего проекта и достигать отличных результатов.
Самое распространённое объяснение ReactJS — это «JavaScript‑библиотека, предоставляющая API для создания потрясающих пользовательских интерфейсов». Но подождите, есть одна важная деталь, которую нам нужно понять — это виртуальный DOM.
Как мы знаем, Document Object Model (DOM) — это строительный блок всех веб‑страниц. Без и��пользования библиотеки вроде ReactJS нам нужно было бы изменять элементы прямо в DOM, что приводило бы к полной перерисовке всей страницы. Это занимает много времени и памяти.
И вот тут на помощь приходит ReactJS с его волшебным инструментом — виртуальным DOM, виртуальной копией реального DOM. Что делает React? Когда что‑то меняется в DOM, он сначала находит эти изменения в виртуальном DOM, сравнивает с реальным и вычисляет, как лучше всего обновить реальный DOM (только изменённую часть). Таким образом, решается основная проблема с перерисовкой всего DOM, и мы получаем инструмент для создания реактивных пользовательских интерфейсов.
Декларативный интерфейс — это традиционный способ создания веб‑страниц, когда вы шаг за шагом говорите браузеру, что делать. Например, найдите элемент с ID «message» и измените текст на «Hello».
React же использует императивный подход, в котором мы описываем конечный результат, который хотим получить, а не процесс. Например, следующий код описывает два состояния кнопки, и она обновляется в соответствии с логикой компонента.
function LoginButton({
isLoggedIn
}) {
if (isLoggedIn) {
return < button > Logout < /button>;
} else {
return < button > Login < /button>;
}
}При написании кода мы обычно используем синтаксис JSX. Это расширение JavaScript. JSX выглядит очень похоже на HTML, но это HTML‑код, написанный внутри JavaScript‑файла. JSX — это основа, которая позволяет нам создавать переиспользуемые компоненты.
Есть несколько важных моментов, которые стоит помнить при написании кода на JSX:
Нужно использовать className вместо class для указания CSS‑классов в элементах. Дело в том, что class — зарезервированное слово в JavaScript для объявления классов, поэтому мы не можем его использовать.
<div className="my-container">...</div>Мы должны использовать camelCase для атрибутов HTML. Например, onclick превращается в onClick, а tabindex — в tabIndex.
Необходимо закрывать все теги. Самозакрывающиеся теги, такие как <br> и <img>, должны быть записаны как <br/> и <img/>.
Компонент должен возвращать только один элемент. Если нужно вернуть несколько, их следует обернуть в родительский элемент.
Компоненты — это основа ReactJS. Это переиспользуемые куски кода, которые имеют свою собственную логику и дизайн. Компоненты дают нам свободу для повторного использования и позволяют разбить сложное приложение на маленькие части. Эти маленькие компоненты легче отлаживать, поддерживать и они обеспечивают отличную производительность.
function WelcomeMessage(props) {
return < h1 > Hello, {
props.name
} < /h1>;
} Традиционный инструмент create‑react‑app — это стандартная цепочка инструментов, предложенная командой React. За последние несколько лет экосистема значительно развилась, и новые инструменты, такие как Vite, предлагают гораздо более быстрый процесс создания и разработки.
npm create vite@latest my-react-app --template react
cd my-react-app
npm install
npm run devnpx create-react-app my-react-app
cd my-react-app
npm startВы можете подумать, что если нам нужно вернуть всего один элемент в компоненте, то нужно создавать лишний <div> или другие контейнеры. React предоставляет решение — фрагменты, которые позволяют обернуть элементы без создания лишних узлов в DOM.
import {
Fragment
} from 'react';
function MyComponent() {
return (
<
Fragment >
<
h1 > My Header < /h1>
<
p > My paragraph. < /p>
<
/Fragment>
);
}
// Or using the short syntax
function AnotherComponent() {
return (
<
>
<
h1 > Another Header < /h1>
<
p > Another paragraph. < /p>
<
/>
);
}JSX позволяет писать JavaScript‑выражения внутри себя. Помните, что в JSX мы оборачиваем этот код в фигурные скобки {}. С помощью этого можно использовать переменные, вызовы функций, арифметические операции или любой валидный JavaScript‑код внутри JSX.
const user = {
name: 'Jane Doe',
avatarUrl: 'path/to/avatar.jpg'
};
function UserProfile() {
const divClass = "profile-card";
return (
<
div className = {
divClass
} >
<
h1 > {
user.name
} < /h1>
<
img src = {
user.avatarUrl
}
alt = {
'Photo of ' + user.name
}
/>
<
p > 2 + 2 = {
2 + 2
} < /p>
<
/div>
);
}Следует помнить, что JSX сам по себе не понимает JavaScript, но такие инструменты, как Babel, транслируют JSX в функциональный JavaScript во время процесса сборки.
И тогда JSX
<h1 className="greeting">Hello, world!</h1>Преобразуется в:
React.createElement(
'h1',
{
className: 'greeting'
},
'Hello, world!'
);Самый практичный способ создать компонент — это создать JavaScript‑функции и возвращать наш JSX внутри них. Мы можем передавать аргументы или свойства в функцию и использовать их внутри. Эти свойства или аргументы, которые мы передаем, называются «props».
const MyComponent = (props) => {
return < h1 > {
props.title
} < /h1>;
};Здесь мы передаем props в компонент MyComponent и используем их внутри. Теперь, если мы хотим использо��ать этот компонент, мы можем сделать это так:
const App = () => {
return (
<
div >
<
MyComponent title = "Hello World" / >
<
/div>
);
};Каждый компонент получает пропс под названием children. Это всё, что передано в компонент внутри его тегов.
const Card = ({
children
}) => {
return (
<
div className = "card-container" >
{
children
}
<
/div>
);
};Теперь всё, что написано между <Card></Card>, считается как children. Например:
const App = () => {
return (
<
Card >
<
h1 > This is the card title < /h1>
<
p > This is the card content. < /p>
<
/Card>
);
};Состояния — это основа реактивного UI. Здесь мы оставляем императивный подход и сосредотачиваемся на декларативном. UI изменяется с изменением состояния. Состояния меняются в ответ на события, это могут быть как события от пользователя, так и от сервера.
Изменение состояния — это изменение UI. Когда меняется значение состояния, компонент перерисовывается.
React изолирует состояния компонентов. Это значит, что если меняется состояние какого‑либо компонента, перерисовывается только этот компонент, а не весь UI.
Если пропсы — это данные, которые мы передаем в компоненты, то состояние — это временная память компонента. Оно запоминает информацию, например, о том, открыт ли выпадающий список или нет.
Хуки — это специальные функции, которые позволяют нам «подключаться» к возможностям React. Самый распространённый из них — хук useState. Он позволяет управлять состоянием компонентов.
Для его использования нужно сначала импортировать его.
import { useState } from 'react';Вызов useState выполняет две вещи:
Он объявляет «переменную состояния».
Он возвращает пару значений: текущее состояние и функцию для его обновления.
const [count, setCount] = useState(0);Здесь мы объявляем count как переменную состояния. Мы устанавливаем её начальное значение равным 0.
А setCount — это функция, которая позволяет обновлять значение count.
Пример:
import React, {
useState
} from 'react';
function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
// We will use the setter function to update the state
setCount(count + 1);
}
return (
<
div >
<
p > You clicked {
count
}
times < /p>
<
button onClick = {
handleClick
} >
Click me
<
/button>
<
/div>
);
}Здесь внутри компонента мы объявляем переменную состояния count и функцию setCount для её обновления.
Функция handleClick() обрабатывает действие, когда пользователь нажимает на кнопку. Когда пользователь нажимает, вызывается функция setCount(). setCount(count + 1) обновляет значение переменной count.
Когда значение переменной состояния изменяется, компонент перерисовывается. После перерисовки будет отображено новое значение count.
JavaScript предоставляет нам API для обработки событий. Аналогично, ReactJS также предоставляет API для обработки событий, но есть несколько отличий.
Во‑первых, мы должны использовать camelCase. Например, событие onclick становится onClick.
Во‑вторых, мы передаем ссылку на функцию в обработчик события, а не строку.
// Incorrect (HTML style)
<button onclick="handleClick()">Click Me</button>
// Correct (React style)
<button onClick={handleClick}>Click Me</button>Обычно мы объявляем функции обработчиков событий внутри компонентов. Затем мы передаем объект события в функцию обработчика.
Пример:
function Form() {
function handleSubmit(event) {
event.preventDefault();
alert('You submitted the form!');
}
return (
<
form onSubmit = {
handleSubmit
} >
<
button type = "submit" > Submit < /button>
<
/form>
);
}Для таких событий, как формы, React предоставляет контролируемые компоненты. Это ничего нового — мы используем хук useState для объявления переменных, которые хранят значения элементов формы. Когда пользователь взаимодействует с формой, состояние изменяется, компонент перерисовывается, и новое значение отображается в форме.
import React, {
useState
} from 'react';
function NameForm() {
const [name, setName] = useState('');
function handleChange(event) {
setName(event.target.value);
}
return (
<
form >
<
label >
Name:
<
input type = "text"
value = {
name
}
onChange = {
handleChange
}
/>
<
/label>
<
p > Your name is: {
name
} < /p>
<
/form>
);
}Здесь, когда пользователь вводит имя,
При вводе первого символа срабатывает onChange(), и функция handleChange вызовет setName() с новым значением.
Значение переменной состояния name обновится, и компонент перерисуется.
Вот где проявляется настоящая магия ReactJS. Теперь мы можем определить, как должен выглядеть наш компонент в зависимости от определённых условий.
JSX не имеет отдельного синтаксиса для условий. Мы используем JavaScript для обработки условий. Существует два способа это сделать:
1. Операторы if/else:
function Greeting({
isLoggedIn
}) {
if (isLoggedIn) {
return < h1 > Welcome back! < /h1>;
}
return < h1 > Please sign up. < /h1>;
}Здесь наш компонент получает пропс isLoggedIn. Затем мы проверяем его значение. Если оно true, мы отображаем «Welcome back!». Если false, показываем «Please sign up.».
2. Тернарный оператор (?:)
function Greeting({
isLoggedIn
}) {
return (
<
div >
{
isLoggedIn ? < h1 > Welcome back! < /h1> : <h1>Please sign up.</h1 >
}
<
/div>
);
}Здесь мы также передаем пропс isLoggedIn. Затем в одной строке проверяем его значение. Если оно true, показываем сообщение после ?. Если оно false, показываем сообщение после :.
Мы редко жёстко прописываем наши списки. Обычно для этого используются массивы JavaScript. Методы массивов, такие как map(), становятся полезными в компонентах для отрисовки списков.
const products = [
{
title: 'Cabbage',
id: 1
},
{
title: 'Garlic',
id: 2
},
{
title: 'Apple',
id: 3
},
];
function ShoppingList() {
const listItems = products.map(product =>
<
li key = {
product.id
} >
{
product.title
}
<
/li>
);
return < ul > {
listItems
} < /ul>;
}Здесь у нас есть массив продуктов. Внутри компонента ShoppingList мы отрисовываем listItems. Метод products.map() возвращает один элемент за раз, в зависимости от индекса. Затем мы рендерим его в компоненте.
Важно понимать роль атрибута key. Атрибут key, переданный в <li key={product.id}>, очень важен при использовании метода map. key — это уникальная идентификация для отрисовываемого элемента. Он помогает React понять, что уже было отрисовано, и рендерить только те элементы, которые ещё не были отрисованы.
На основе key React решает, какие элементы рендерить. Это предотвращает перерисовку всего списка, что критично для производительности.
React‑хуки — это упрощённая версия ранее используемых методов для классовых компонентов. Хуки — это настоящие герои, которые позволяют нам создавать функциональные компоненты.
Запомните простое правило: каждый хук начинается с слова «use». Например, useState или useEffect — это хуки, начинающиеся с «use».
Представьте побочные эффекты как операции, которые изменяют что‑то вне нашего компонента. Для обработки изменений внутри компонентов мы используем useState.
Примеры побочных эффектов:
Получение данных из API (AJAX‑запросы).
Установка таймера с помощью setTimeout или setInterval.
Хук useEffect() выполняется после рендеринга компонента. Например, у нас есть компонент дашборда. Сначала будет загружен сам дашборд. После этого useEffect() получит данные из внешнего API. После получения данных, в зависимости от изменений, компонент будет перерисован.
useEffect(() => { callback function }, [dependencies]);useEffect принимает два аргумента. Первый — это callback‑функция или код эффекта. Второй — это массив зависимостей.
1. Массив зависимостей — это ключевой момент, который нужно понять. Он задаёт условия, при которых useEffect() будет выполняться снова. Если мы не передаём массив зависимостей, то useEffect() будет запускаться при каждом перерисовывании компонента. Это может быть неэффективно. Представьте, если каждый раз будет выполняться запрос за данными к внешнему API.
useEffect(() => {
// Runs on initial render AND every update
console.log('Component rendered or updated');
});2. Передавая null (или пустой массив) в качестве массива зависимостей, useEffect() будет выполняться только после первого рендера компонента. Он не будет запускаться после последующих обновлений компонента.
useEffect(() => {
// Runs only once after the first render
fetchData();
}, );3. Передавая значения в массив зависимостей, мы гарантируем, что useEffect() будет выполняться после первого рендера компонента и каждый раз, когда значение в массиве зависимостей изменяется.
useEffect(() => {
// Runs when the component mounts and whenever `userId` changes
fetchUserData(userId);
}, [userId]);Здесь useEffect() выполнится после первого рендера компонента. После этого он будет запускаться только в случае изменения userId. Если userId изменится, useEffect() выполнится, и компонент будет перерисован с новыми данными.
Когда наше приложение становится большим, нам нужно передавать пропсы от одного компонента к другому на несколько уровней. Иногда промежуточные компоненты даже не используют эти пропсы. Мы называем эту проблему «Prop Drilling».
Хук useContext решает эту проблему. Он позволяет нам объявить пропсы как глобальные для определённого дерева компонентов. Дочерние компоненты этого дерева могут использовать глобальные пропсы без передачи их через каждый уровень.
1. Создаем контекст
// theme-context.js
import {
createContext
} from 'react';
export const ThemeContext = createContext('light');Здесь мы импортируем createContext из React. Затем мы объявляем контекст с именем ThemeContext. Здесь мы задаём значение по умолчанию для контекста — «light».
2. Предоставляем контекст
Теперь, после создания контекста, мы должны предоставить его с помощью Provider. Каждый компонент внутри этого Provider и их дочерние компоненты могут использовать контекст без необходимости передавать его через каждый уровень.
// App.js
import {
ThemeContext
} from './theme-context';
function App() {
const theme = 'dark'; // initial value was light
return (
<
ThemeContext.Provider value = {
theme
} >
<
Toolbar / >
<
/ThemeContext.Provider>
);
}3. Используем контекст
Теперь, внутри любого дочернего компонента, мы можем получить значение контекста, используя хук useContext.
// Toolbar.js
import {
useContext
} from 'react';
import {
ThemeContext
} from './theme-context';
function Toolbar() {
const theme = useContext(ThemeContext); // theme will be 'dark'
return < div className = {
`theme-${theme}`
} > ... < /div>;
}Мы уже знаем, как использовать useState. Мы понимаем, как управлять состоянием и обновлять его значения.
Но по мере роста проекта, логика состояния становится сложной. Иногда нужно управлять несколькими состояниями одновременно. Здесь на помощь приходят хуки, такие как useReducer. Они помогают эффективно управлять множественными состояниями.
Это трёхэтапный процесс:
Мы объявляем переменную состояния.
Затем у нас есть действия, которые описывают, что произошло (например, INCREMENT или DECREMENT).
Третье — это функция‑редуктор, которая принимает переменную и действие как входные данные и возвращает обновлённое значение переменной состояния.
const initialState = {
count: 0
};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {
count: state.count + 1
};
case 'decrement':
return {
count: state.count - 1
};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<
>
Count: {
state.count
}
<
button onClick = {
() => dispatch({
type: 'decrement'
})
} > - < /button>
<
button onClick = {
() => dispatch({
type: 'increment'
})
} > + < /button>
<
/>
);
}Существует два основных случая использования useRef. Во‑первых, для доступа к элементам DOM. Во‑вторых, для хранения изменяемых значений.
1. Он позволяет получить доступ к элементам DOM.
Компоненты рендерят элементы в DOM. useRef предоставляет доступ к этим элементам.
import {
useRef,
useEffect
} from 'react';
function TextInputWithFocusButton() {
const inputEl = useRef(null);
useEffect(() => {
// Focus the input element on component render
inputEl.current.focus();
}, );
return < input ref = {
inputEl
}
type = "text" / > ;
}Здесь наш компонент создаёт элемент <input>, и мы используем ref="inputEl" для ссылки на него. Затем мы используем useRef, чтобы получить доступ к этому элементу и сохранить его в переменной inputEl. Здесь мы устанавливаем фокус на этом элементе.
2. Второй вариант — для хранения изменяемых переменных (например, items).
useRef также возвращает изменяемое свойство .current объекта. Значение .current существует на протяжении всего жизненного цикла компонента. Изменение значения .current не вызывает перерисовку компонента.
Это полезно в ситуациях, когда, например, нам нужно сохранить текущее время в переменной, но изменения этой переменной ��е должны инициировать перерисовку компонента. Здесь мы можем использовать мощность свойства .current у useRef.
import {
useRef,
useState
} from "react";
function TimerExample() {
const countRef = useRef(0); // stays across renders
const [renderCount, setRenderCount] = useState(0);
function handleClick() {
countRef.current += 1; // updates ref (no re-render)
setRenderCount(c => c + 1); // triggers re-render just to show result
}
return ( <
div >
<
p > Ref value: {
countRef.current
} < /p> <
p > Render count: {
renderCount
} < /p> <
button onClick = {
handleClick
} > Increase < /button> <
/div>
);
}
export default TimerExample;Прежде всего, важно понять, что в ReactJS каждый раз, когда мы объявляем функцию или объект, создаётся абсолютно новый экземпляр. Даже если мы объявим два одинаковых объекта, переменных или функций, они будут считаться разными.
const obj1 = { a: 1 };
const obj2 = { a: 1 };
obj1 === obj2; // false, they have different references in memoryЭто может вызвать проблемы с производительностью в таких ситуациях, как пропс‑дриллинг.
Хук useMemo кэширует (мемоизирует) результаты вычислений. Он пересчитывает значение только в случае, если изменяется хотя бы одна из зависимостей.
Пример: если у нас есть большой список объектов, и наш компонент должен отфильтровать и отсортировать этот список, то выполнение этих операций при каждом рендере будет очень неэффективным. В таком случае мы используем хук useMemo. Он будет фильтровать и сортировать список только в том случае, если сам список изменится или изменятся критерии фильтрации или сортировки.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);После того, как мы решили предыдущую проблему с производительностью, представьте, что нам нужно передать какую‑либо функцию вычислений как пропс в дочерний компонент.
Каждый раз, когда что‑то изменяется в дочернем компоненте, он будет перерисовываться. И при каждом рендере React будет пытаться обновить переданную пропс‑функцию. Это снова создаёт ту же проблему, что и раньше.
Здесь мы также будем использовать useMemo. Мы не можем передать useMemo напрямую как пропс, поэтому создадим callback‑функцию с помощью useMemo и передадим её как пропс.
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);Но помните, что эти оптимизирующие хуки не бесплатны. У них есть собственная стоимость по производительности и памяти, используемой для кэширования. Поэтому их стоит использовать только в том случае, если вычисления, которые мы оптимизируем, действительно сложные.
React предоставляет множество хуков для различных ситуаций. Также он позволяет создавать кастомные хуки с использованием существующих хуков для специфических задач.
Например, если нам нужно получить данные пользователя с API, и в зависимости от ответа API нужно управлять состояниями, такими как загрузка, ошибка и данные пользователя, то вместо использования нескольких useState и useEffect, мы можем создать собственный кастомный хук. Это упростит процесс.
Пример, как можно создать кастомный хук useFetch, чтобы решить эту задачу:
import {
useState,
useEffect
} from 'react';
function useFetch(url) {
const = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch(url)
.then(response => response.json())
.then(data => setData(data))
.catch(error => setError(error))
.finally(() => setLoading(false));
}, [url]); // Re-fetch if the URL changes
return {
data,
loading,
error
};
}
// Now, using the custom hook in a component is clean and simple:
function UserProfile({
userId
}) {
const {
data,
loading,
error
} = useFetch(`https://api.example.com/users/${userId}`);
if (loading) return < p > Loading... < /p>;
if (error) return < p > Error! < /p>;
return < h1 > {
data.name
} < /h1>;
}Здесь, когда функция userProfile выполняется, она вызывает useFetch. Как мы определили внутри useFetch, это запускает useEffect, который выполняет запрос данных с указанного URL. После получения данных устанавливаются значения для переменных loading, error и data.
После этого компонент перерисовывается с новыми значениями. Мы передаём массив зависимостей [url], поэтому useEffect будет выполняться снова только при изменении url.
В традиционных приложениях мы обычно загружали новую страницу с сервера для требуемой страницы. Однако в одностраничных приложениях меняется только компонент, а не вся страница. Изменение компонента происходит на стороне клиента, а не на сервере.
Именно поэтому нам нужно что‑то особенно�� для управления навигацией. Стандартное решение — это React Router.
React Router не входит в стандартный пакет React, и его нужно установить отдельно, используя библиотеку react-router-dom, прежде чем начать использовать.
npm install react-router-domReact Router предоставляет несколько ключевых компонентов:
<BrowserRouter>: Для использования React Router, нужно обернуть всё приложение внутри этого компонента. Или хотя бы ту часть приложения, где требуется навигация с помощью React Router.
<Routes>: Этот компонент проверяет дочерние компоненты <Route> и переходит к первому, который соответствует текущему URL.
<Route path="/some-path" element={<SomeComponent />}>: Это основной компонент. Он рендерит указанный компонент, если путь совпадает.
<Link to="/some-path">: Используется для создания ссылок. Мы используем его вместо тега <a>. Тег <a> вызывает полное обновление страницы, в то время как тег <Link> инициирует рендеринг конкретного компонента. Если путь совпадает с любым из <Route>, будет отрисован соответствующий компонент.
import {
BrowserRouter,
Routes,
Route,
Link
} from "react-router-dom";
function App() {
return (
<
BrowserRouter >
<
nav >
<
Link to = "/" > Home < /Link> | <Link to="/about
">About</Link>
<
/nav>
<
Routes >
<
Route path = "/"
element = {
< Home / >
}
/>
<
Route path = "/about"
element = {
< About / >
}
/>
<
/Routes>
<
/BrowserRouter>
);
}
export default App;В приведённом коде мы обернули всё приложение внутри <BrowserRouter>.
Внутри элемента <nav> мы используем <Link>, чтобы создать динамические ссылки. Первая ссылка ведет на путь /, а вторая — на путь /about.
Если кто‑то кликает на первую ссылку, React Router просматривает <Routes>. Компонент <Routes> проверяет соответствие пути с <Route> внутри него. Если путь совпадает с /, он рендерит указанный компонент, например, здесь это будет компонент <Home />.
Часто возникает необходимость защитить наши маршруты. Например, показывать дашборд только для авторизованных пользователей.
Для решения этой проблемы создаются защищённые маршруты. Это не что‑то принципиально отличное от того, что мы разобрали выше — это просто другой способ реализации.
Компонент Navigate из react-router-dom помогает нам напрямую перенаправить пользователя на указанный путь в зависимости от условий.
import {
Navigate
} from 'react-router-dom';
// This component assumes you have an `auth` object from a context or state management solution
const ProtectedRoute = ({
auth,
children
}) => {
if (!auth.isAuthenticated) {
// Redirect them to the /login page, but save the current location they were
// trying to go to. This allows us to send them along to that page after a
// successful login.
return < Navigate to = "/login"
replace / > ;
}
return children;
};import {
BrowserRouter,
Routes,
Route,
Link
} from "react-router-dom";
import ProtectedRoute from "./ProtectedRoute";
import Dashboard from "./Dashboard";
import Home from "./Home";
import Login from "./Login";
function App() {
const authContext = {
isAuthenticated: true
}; // example
return (
<
BrowserRouter >
<
nav >
<
Link to = "/" > Home < /Link> | <Link to="/dashboard
">Dashboard</Link>
<
/nav>
<
Routes >
<
Route path = "/"
element = {
< Home / >
}
/>
<
Route path = "/login"
element = {
< Login / >
}
/>
<
Route
path = "/dashboard"
element = {
<
ProtectedRoute auth = {
authContext
} >
<
Dashboard / >
<
/ProtectedRoute>
}
/>
<
/Routes>
<
/BrowserRouter>
);
}
export default App;Здесь мы создаём компонент ProtectedRoute, который принимает два аргумента: auth и children. В зависимости от условий, если пользователь не авторизован, он будет автоматически перенаправлен на маршрут «login».
Если пользователь авторизован, то возвращаются children, которые могут быть любыми, например, целый компонент Dashboard.
Во многих приложениях требуется проверять состояние аутентификации пользователя на разных страницах. Поэтому лучше создать глобальный контекст аутентификации и обернуть приложение внутри этого контекста. Теперь на каждом шаге или в компоненте мы можем проверять аутентификацию и рендерить компоненты в зависимости от состояния. Для этого мы будем использовать useContext, useState, useEffect и localStorage.
Сначала создадим контекст аутентификации, который будет хранить состояние аутентификации и функцию для его изменения. Также мы создадим функцию useAuth(), чтобы упростить использование контекста аутентификации. Функция useAuth() нужна для того, чтобы не писать каждый раз длинную конструкцию useContext(AuthContext), а просто использовать useAuth().
// AuthContext.js
import {
createContext,
useContext
} from 'react';
const AuthContext = createContext(null);
export const useAuth = () => {
return useContext(AuthContext);
};
export default AuthContext;Этот компонент будет оборачивать всё наше приложение. Он будет управлять состоянием аутентификации и предоставлять функции для входа и выхода с использованием контекста.
Не паникуйте, взгляните на следующий код:
// AuthProvider.js
import React, {
useState,
useEffect
} from 'react';
import AuthContext from './AuthContext';
export const AuthProvider = ({
children
}) => {
const [user, setUser] = useState(() => {
// Get stored user from localStorage
const savedUser = localStorage.getItem('user');
return savedUser ? JSON.parse(savedUser) : null;
});
useEffect(() => {
// Update localStorage when user state changes
if (user) {
localStorage.setItem('user', JSON.stringify(user));
} else {
localStorage.removeItem('user');
}
}, [user]);
const login = (userData) => {
// In a real app, you would verify credentials here
setUser({
name: userData.name
});
};
const logout = () => {
setUser(null);
};
const value = {
user,
login,
logout
};
return < AuthContext.Provider value = {
value
} > {
children
} < /AuthContext.Provider>;
};Мы создаём компонент AuthProvider. Мы используем useState для управления состоянием пользователя и setUser() для обновления его значения. Значение пользователя сохраняется в localStorage, чтобы обеспечить постоянство состояния пользователя. Сначала компонент проверяет localStorage и пытается найти состояние пользователя. Если оно найдено, оно сохраняет его значение. Если же состояние не найдено, то сохраняется значение по умолчанию «null». Хук useEffect() обновляет значение пользователя в localStorage, когда состояние пользователя изменяется. Он запускается каждый раз, когда значение пользователя меняется, и сохраняет новое значение в localStorage. Функция login проверяет пользователя и инициирует изменение состояния пользователя. Функция logout просто изменяет состояние пользователя на «null». В конце компонент возвращает AuthContext.Provider с тремя значениями: user, login и logout.
Мы оборачиваем всё приложение с использованием контекста AuthProvider. Теперь все дочерние компоненты внутри него имеют доступ к AuthContext.
// main.jsx
import { AuthProvider } from './AuthProvider';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<AuthProvider>
<App />
</AuthProvider>
</React.StrictMode>
);Теперь любой компонент может напрямую использовать AuthContext. Для удобства доступа, мы можем просто использовать хук useAuth() для получения значений.
// Navbar.js
import { useAuth } from './AuthContext';
function Navbar() {
const { user, logout } = useAuth();
return (
<nav>
{user? (
<>
<span>Welcome, {user.name}</span>
<button onClick={logout}>Logout</button>
</>
) : (
<span>Please log in</span>
)}
</nav>
);
}Таким образом, мы можем создать надёжную систему аутентификации для наших приложений.
Я думаю, мы много чего узнали. Но помните, что это не полноценное руководство по React, а всего лишь шпаргалка, которая помогает освежить ваши концепции. Она поможет вам быстро повторить основные компоненты библиотеки ReactJS.
Существует много других аспектов, которые стоит изучить, например, как создавать authContext, как аутентифицировать пользовательский ввод с использованием сторонних библиотек и многое другое.
Удачи вам в вашем дальнейшем обучении. Оставляйте комментарии с вопросами, если они есть — буду рад на них ответить!
React.js сегодня — основа большинства современных веб‑приложений. Если вы хотите понимать, как устроены интерфейсы, управлять состоянием и создавать действительно удобные решения, обратите внимание на курс React.js Developer. Чтобы узнать, подойдет ли вам программа курса, пройдите вступительный тест.

Рост в IT быстрее с Подпиской — дает доступ к 3-м курсам в месяц по цене одного. Подробнее