Всем привет! Меня зовут Анастасия Нечепоренко, я QA Lead и преподаватель курса "JavaScript QA Engineer" в Отус. Добро пожаловать в ещё одну шпаргалку по React! Но подождите, это не то, что вы подумали — не просто набор случайных примеров кода и банальных объяснений, как в других шпаргалках.

Обещаю, это будет по‑настоящему полезно. Мы вместе разберёмся, как работает ReactJS, и как реализованы все его крутые фичи.

Я буду охватывать всё — от самых основ до новейших фич ReactJS 2025 года. Цель этой шпаргалки проста: это должен быть тот единственный документ, который вам нужен для того, чтобы эффективно писать код для следующего проекта и достигать отличных результатов.

Часть 1: Современная основа React

Самое распространённое объяснение ReactJS — это «JavaScript‑библиотека, предоставляющая API для создания потрясающих пользовательских интерфейсов». Но подождите, есть одна важная деталь, которую нам нужно понять — это виртуальный DOM.

Как мы знаем, Document Object Model (DOM) — это строительный блок всех веб‑страниц. Без использования библиотеки вроде ReactJS нам нужно было бы изменять элементы прямо в DOM, что приводило бы к полной перерисовке всей страницы. Это занимает много времени и памяти.

И вот тут на помощь приходит ReactJS с его волшебным инструментом — виртуальным DOM, виртуальной копией реального DOM. Что делает React? Когда что‑то меняется в DOM, он сначала находит эти изменения в виртуальном DOM, сравнивает с реальным и вычисляет, как лучше всего обновить реальный DOM (только изменённую часть). Таким образом, решается основная проблема с перерисовкой всего DOM, и мы получаем инструмент для создания реактивных пользовательских интерфейсов.

Декларативный vs. Императивный интерфейс

Декларативный интерфейс — это способ создания веб-страниц, при котором вы описываете, что должно быть на экране, а не как этого достичь. Например, в React вы просто указываете, какой должен быть интерфейс, а фреймворк сам решает, как обновить DOM, основываясь на этом описании. Императивный подход — это когда вы пошагово указываете браузеру, что делать. Например, найти элемент с ID «message» и изменить текст на «Hello». React использует декларативный подход, в котором вы описываете конечный результат, который хотите получить, а не процесс.

Например, следующий код описывает два состояния кнопки, и она обновляется в соответствии с логикой компонента.

function LoginButton({ isLoggedIn }) {
    if (isLoggedIn) {
        return <button>Logout</button>;
    } else {
        return <button>Login</button>;
    }
}

JSX (Несколько важных моментов)

При написании кода мы обычно используем синтаксис 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>;
}

Vite vs. Create React App (CRA)

Традиционный инструмент create‑react‑app — это стандартная цепочка инструментов, предложенная командой React. В 2025 году она считается устаревшей и более не поддерживается, однако было бы странно не упомянуть ее так сказать для общего развития. К тому же вы все еще можете использовать ее в своих пет-проектах и на проектах, которые еще не переведены на более актуальные способы создания и разработки приложений.

Последние несколько лет экосистема значительно развилась, и новые инструменты, такие как Vite, предлагают ускоренный в несколько раз, относительно традиционного, процесс создания и разработки.

Vite (Рекомендуется):

npm create vite@latest my-react-app --template react

cd my-react-app

npm install

npm run dev

Create React App:

npx 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>
        </>
    );
}

Встраивание JavaScript в компоненты

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 }

Каждый компонент получает пропс под названием 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 изменяется с изменением состояния. Состояния меняются в ответ на события, это могут быть как события от пользователя, так и от сервера.

Изменение состояния — это изменение UI. Когда меняется значение состояния, компонент перерисовывается.

React изолирует состояния компонентов. Это значит, что если меняется состояние какого‑либо компонента, перерисовывается только этот компонент, а не весь UI.

Если пропсы — это данные, которые мы передаем в компоненты, то состояние — это временная память компонента. Оно запоминает информацию, например, о том, открыт ли выпадающий список или нет.

Хук useState

Хуки — это специальные функции, которые позволяют нам «подключаться» к возможностям React. Самый распространённый из них — хук useState. Он позволяет управлять состоянием компонентов.

Для его использования нужно сначала импортировать его.

import { useState } from 'react';

Вызов useState выполняет две вещи:

  1. Он объявляет «переменную состояния».

  2. Он возвращает пару значений: текущее состояние и функцию для его обновления.

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, показываем сообщение после :.

Отрисовка списков с помощью .map()

Мы редко жёстко прописываем наши списки. Обычно для этого используются массивы 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-хуки

React‑хуки — это упрощённая версия ранее используемых методов для классовых компонентов. Хуки — это настоящие герои, которые позволяют нам создавать функциональные компоненты.

Запомните простое правило: каждый хук начинается с слова «use». Например, useState или useEffect — это хуки, начинающиеся с «use».

useEffect: Управление побочными эффектами

Представьте побочные эффекты как операции, которые изменяют что‑то вне нашего компонента. Для обработки изменений внутри компонентов мы используем 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() выполнится, и компонент будет перерисован с новыми данными.

useContext: Избежание "Prop Drilling"

Когда наше приложение становится большим, нам нужно передавать пропсы от одного компонента к другому на несколько уровней. Иногда промежуточные компоненты даже не используют эти пропсы. Мы называем эту проблему «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>;
}

useReducer: Для сложной логики состояния

Мы уже знаем, как использовать 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: Выходной механизм

Существует два основных случая использования 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();
    }, []); // The empty dependency array ensures this effect runs only once on mount

    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 кэширует (мемоизирует) результаты вычислений. Он пересчитывает значение только в случае, если изменяется хотя бы одна из зависимостей.

Пример: если у нас есть большой список объектов, и наш компонент должен отфильтровать и отсортировать этот список, то выполнение этих операций при каждом рендере будет очень неэффективным. В таком случае мы используем хук useMemo. Он будет фильтровать и сортировать список только в том случае, если сам список изменится или изменятся критерии фильтрации или сортировки.

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

useMemo + useCallback

После того, как мы решили предыдущую проблему с производительностью, представьте, что нам нужно передать какую‑либо функцию вычислений как пропс в дочерний компонент.

Каждый раз, когда что‑то изменяется в дочернем компоненте, он будет перерисовываться. И при каждом рендере 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 [data, setData] = useState(null); // Corrected the missing 'data' state
    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 Router не входит в стандартный пакет React, и его нужно установить отдельно, используя библиотеку react-router-dom, прежде чем начать использовать.

npm install react-router-dom

React Router предоставляет несколько ключевых компонентов:

  1. <BrowserRouter>: Для использования React Router, нужно обернуть всё приложение внутри этого компонента. Или хотя бы ту часть приложения, где требуется навигация с помощью React Router.

  2. <Routes>: Этот компонент проверяет дочерние компоненты <Route> и переходит к первому, который соответствует текущему URL.

  3. <Route path="/some-path" element={<SomeComponent />}>: Это основной компонент. Он рендерит указанный компонент, если путь совпадает.

  4. <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

Этот компонент будет оборачивать всё наше приложение. Он будет управлять состоянием аутентификации и предоставлять функции для входа и выхода с использованием контекста.

Не паникуйте, взгляните на следующий код:

// 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 React from 'react';
import ReactDOM from 'react-dom/client';
import { AuthProvider } from './AuthProvider';
import App from './App'; // Make sure to import your App component

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. Чтобы узнать, подойдет ли вам программа курса, пройдите вступительный тест.

Отзыв студента курса React.js Developer
Отзыв студента курса React.js Developer

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

Комментарии (13)


  1. artptr86
    22.10.2025 12:33

    это не то, что вы подумали — не просто набор случайных примеров кода и банальных объяснений, как в других шпаргалках.

    Обещаю, это будет по‑настоящему полезно. Мы вместе разберёмся, как работает ReactJS, и как реализованы все его крутые фичи «за кулисами», с помощью интерактивных демо

    Неужели снова обманули? :)


    1. SiYa_renko Автор
      22.10.2025 12:33

      Упс, дефект перевода! Держала в голове, что нужно это убрать, и под конец забыла. Но в оригинальной статье можно посмотреть интерактивные демо, они отличные)

      Спасибо что заметили :)


      1. artptr86
        22.10.2025 12:33

        Тем не менее, автор обещал разобрать как реализованы фичи Реакта «за кулисами», но и в оригинальной статье этого нет. В результате получился «просто набор случайных примеров кода и банальных объяснений, как в других шпаргалках».


        1. SiYa_renko Автор
          22.10.2025 12:33

          Имхо смысл статьи заключается в том, чтобы посмотреть, как работают основные фичи React с точки зрения практического использования, а не с точки зрения их внутренней реализации) да и на случайный набор примеров она, по моему мнению, не катит.

          Для новичков (да и не только) весьма полезно, поскольку как раз рассматривает основные фичи


          1. JerryI
            22.10.2025 12:33

            Тогда при чем тут фичи «за кулисами»


          1. Tujger
            22.10.2025 12:33

            Для новичков вредно, что в самом начале неверно обозначены императивный и декларативный подходы. Дальше читать не стал.


  1. goremukin
    22.10.2025 12:33

    Декларативность и императивность перепутаны


  1. JerryI
    22.10.2025 12:33

    Косяки в самом начале:

            <
            Fragment >
    
            <
            h1 > My Header < /h1>
    
            <
            p > My paragraph. < /p>
    
            <
            /Fragment>


  1. morett1m
    22.10.2025 12:33

    очень странная gpt статья про базовые вещи, которые знают все


  1. Wintz
    22.10.2025 12:33

    Традиционный инструмент create‑react‑app — это стандартная цепочка инструментов, предложенная командой React

    CRA - деприкейтит. Нельзя же переводить бездумно старые статьи


    1. winkyBrain
      22.10.2025 12:33

      Вот что бывает, когда QA рассказывает про JS


  1. evil_kabab
    22.10.2025 12:33

    Пропс, пропсы - забавно звучит когда к английскому множественному числу добавляется еще и русское. Prop - это property. Не путать с реквизитом на съемочной площадке


    1. SiYa_renko Автор
      22.10.2025 12:33

      Да, в английском “props” действительно множественное, но в русском это слово прижилось как отдельный термин — по тому же принципу, как “чипсы” или “шорты”. Так что “пропсы” звучит нормально для React-сообщества и давно стало частью профессионального сленга. В том числе если вы откроете официальную документацию реакт, вы увидите, что там используются именно пропсы. Даже в английском форму prop используют редко в живой речи или коде