← Назад к курсу

Учебное пособие по React

Это учебное пособие по React рассчитано на читателей, знакомых с основами JavaScript (ES6+) и предназначено для изучения с нуля до продвинутого уровня. В конце каждого раздела есть краткие выводы.

1. Введение в React

React — JavaScript-библиотека для построения пользовательских интерфейсов, разработанная Facebook (ныне Meta). Её ключевые особенности:

  • Декларативный подход — вы описываете, как интерфейс должен выглядеть для каждого состояния, а React сам обновляет DOM.
  • Компонентная архитектура — интерфейс разбивается на независимые, переиспользуемые части (компоненты).
  • Виртуальный DOM — React поддерживает «виртуальное» представление интерфейса, что позволяет эффективно применять минимально необходимые изменения к реальному DOM.
  • Односторонний поток данных — данные передаются от родительских компонентов к дочерним через props.

React не является полноценным фреймворком (в нём нет маршрутизации, глобального хранилища «из коробки»), но вместе с дополнительными библиотеками (React Router, Redux и т. д.) образует мощную экосистему.

2. Настройка окружения

Требования

  • Node.js (версия 14 или выше)
  • npm или yarn

Создание проекта

Современный способ (рекомендуется): Vite

npm create vite@latest my-app -- --template react
cd my-app
npm install
npm run dev

Альтернатива (устаревшая): Create React App

npx create-react-app my-app
cd my-app
npm start

Структура минимального проекта (Vite + React)

my-app/
  ├── index.html         # корневой HTML-файл
  ├── src/
  │    ├── main.jsx      # точка входа, рендер приложения
  │    ├── App.jsx       # корневой компонент
  │    └── ...
  └── package.json

3. JSX: синтаксис расширения JavaScript

JSX позволяет писать HTML-подобный код внутри JavaScript. Он обязателен при использовании React (хотя существуют альтернативы, но JSX — стандарт).

Базовые правила

  1. Возвращайте один корневой элемент (можно обернуть в <div> или React.Fragment <>...</>).
  2. Закрывайте все теги — <img />, <br />.
  3. Имена свойств — camelCase: className вместо class, htmlFor вместо for.
  4. Встраивание выражений — через фигурные скобки {}.
const name = "Иван";
const element = <h1>Привет, {name}!</h1>;

Пример компонента, использующего JSX

function Greeting({ user }) {
  const age = 25;
  return (
    <div className="greeting">
      <p>Имя: {user.name}</p>
      <p>Возраст: {age}</p>
      <button onClick={() => alert("Привет!")}>Нажми</button>
    </div>
  );
}

4. Компоненты: функциональные и классовые

Функциональные компоненты (современный стандарт)

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

Компоненты-классы (исторические, можно не изучать глубоко)

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

Передача данных через props — только для чтения, компонент не может менять свои пропсы.

children (вложенные элементы)

function Card({ children, title }) {
  return (
    <div className="card">
      <h2>{title}</h2>
      {children}
    </div>
  );
}

// Использование
<Card title="Мой пост">
  <p>Это содержимое внутри карточки</p>
</Card>

5. Состояние (State) и хук useState

Состояние — это данные, которые могут меняться внутри компонента и вызывать его перерисовку.

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0); // начальное значение 0

  const increment = () => setCount(count + 1);
  const decrement = () => setCount(c => c - 1); // функция обновления через предыдущее значение

  return (
    <div>
      <p>Счёт: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
}

Правила хуков:

  • Вызывайте хуки только на верхнем уровне компонента (не внутри условий, циклов).
  • Вызывайте хуки только из функциональных компонентов или кастомных хуков.

Обновление объектов и массивов

Состояние должно обновляться иммутабельно (новый объект/массив).

const [user, setUser] = useState({ name: 'Анна', age: 30 });

// правильно
setUser({ ...user, age: 31 });

const [list, setList] = useState([1, 2, 3]);
setList([...list, 4]); // добавление элемента
setList(list.filter(item => item !== 2)); // удаление

6. Побочные эффекты: useEffect

useEffect выполняет код после рендеринга компонента (запросы к API, подписки, изменение DOM, таймеры).

Базовый синтаксис

useEffect(() => {
  // эффект
  return () => {
    // очистка (опционально)
  };
}, [dependencies]);

Три варианта использования

Зависимости Когда выполняется
[] (пустой массив) Один раз при монтировании компонента
[dep1, dep2] При первом рендере и при каждом изменении dep1 или dep2
без второго аргумента При каждом рендере (включая первый) – используйте редко

Пример: загрузка данных

import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    let ignore = false;
    fetch(`https://api.example.com/users/${userId}`)
      .then(res => res.json())
      .then(data => {
        if (!ignore) setUser(data);
        setLoading(false);
      });
    return () => { ignore = true; }; // предотвращаем гонку запросов
  }, [userId]);

  if (loading) return <p>Загрузка...</p>;
  return <div>{user.name}</div>;
}

7. Обработка событий

События в React называются в camelCase и передаются как функции (не строки).

function ActionButton() {
  function handleClick(event) {
    console.log('Нажата кнопка', event);
  }

  const handleParam = (param) => (event) => {
    console.log(param, event);
  };

  return (
    <>
      <button onClick={handleClick}>Обычная обработка</button>
      <button onClick={handleParam('привет')}>С параметром</button>
    </>
  );
}

8. Условный рендеринг и списки

Условный рендеринг

function Greeting({ isLoggedIn }) {
  return (
    <div>
      {isLoggedIn ? <Dashboard /> : <Login />}
      {isLoggedIn && <LogoutButton />}   // короткий синтаксис: если true, то элемент показан
    </div>
  );
}

Рендер списков

Всегда добавляйте уникальный key (не индекс массива, если порядок может меняться).

const todos = [
  { id: 1, text: 'Изучить React' },
  { id: 2, text: 'Сделать проект' }
];

function TodoList() {
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  );
}

9. Формы и управляемые компоненты

В React обычно используют управляемые компоненты — данные формы хранятся в состоянии.

import { useState } from 'react';

function LoginForm() {
  const [form, setForm] = useState({ email: '', password: '' });

  const handleChange = (e) => {
    setForm({ ...form, [e.target.name]: e.target.value });
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log(form);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="email"
        value={form.email}
        onChange={handleChange}
        placeholder="Email"
      />
      <input
        name="password"
        type="password"
        value={form.password}
        onChange={handleChange}
      />
      <button type="submit">Войти</button>
    </form>
  );
}

10. Подъём состояния (Lifting State Up)

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

function TemperatureInput({ temperature, onTemperatureChange, scale }) {
  return (
    <fieldset>
      <legend>Введите температуру в {scale === 'c' ? 'Цельсиях' : 'Фаренгейтах'}:</legend>
      <input value={temperature} onChange={e => onTemperatureChange(e.target.value)} />
    </fieldset>
  );
}

function Calculator() {
  const [celsius, setCelsius] = useState('');
  const [fahrenheit, setFahrenheit] = useState('');

  const handleCelsiusChange = (value) => {
    setCelsius(value);
    setFahrenheit(convertToFahrenheit(value));
  };

  // аналогично для fahrenheit...

  return (
    <>
      <TemperatureInput scale="c" temperature={celsius} onTemperatureChange={handleCelsiusChange} />
      <TemperatureInput scale="f" temperature={fahrenheit} onTemperatureChange={handleFahrenheitChange} />
    </>
  );
}

11. Контекст (Context API)

Контекст позволяет передавать данные глубоко в дерево компонентов без пробрасывания пропсов «через каждую прослойку».

Создание и использование

import { createContext, useContext, useState } from 'react';

const ThemeContext = createContext('light');

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button className={theme}>Кнопка</button>;
}

function App() {
  const [theme, setTheme] = useState('dark');
  return (
    <ThemeContext.Provider value={theme}>
      <ThemedButton />
      <button onClick={() => setTheme(t => t === 'dark' ? 'light' : 'dark')}>Переключить тему</button>
    </ThemeContext.Provider>
  );
}

12. Рефы (Refs) – useRef

  • Хранение значений, не вызывающих перерендер (переменные экземпляра).
  • Прямой доступ к DOM-элементам.
import { useRef } from 'react';

function AutoFocusInput() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return <input ref={inputRef} />;
}

Важно: меняйте ref.current только в обработчиках или эффектах, не в рендере.

13. Маршрутизация (React Router)

Установка: npm install react-router-dom

Базовый пример

import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
import Home from './Home';
import About from './About';

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

14. Мемоизация (оптимизация)

React.memo – предотвращает перерисовку компонента, если пропсы не изменились

const ExpensiveChild = React.memo(function ExpensiveChild({ data }) {
  // рендер тяжёлого компонента
});

useMemo – кеширует результат вычисления

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

useCallback – кеширует функцию

const handleClick = useCallback(() => {
  doSomething(a, b);
}, [a, b]);

Используйте эти инструменты только при реальных проблемах производительности.

15. Продвинутые хуки: useReducer

useReducer — альтернатива useState для сложной логики обновления состояния (несколько под-значений, следующие обновления зависят от предыдущих).

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 };
    case 'reset': return initialState;
    default: throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </>
  );
}

16. Работа с API (асинхронные запросы)

Лучшие практики:

  • Использовать useEffect с очисткой.
  • Обрабатывать состояния загрузки и ошибок.
  • Отменять запросы при размонтировании (AbortController).
function useFetch(url) {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const abortController = new AbortController();
    setLoading(true);

    fetch(url, { signal: abortController.signal })
      .then(res => res.json())
      .then(setData)
      .catch(err => {
        if (err.name !== 'AbortError') setError(err);
      })
      .finally(() => setLoading(false));

    return () => abortController.abort();
  }, [url]);

  return { data, error, loading };
}

17. Кастомные хуки

Позволяют переиспользовать логику с состоянием. Имя хука должно начинаться с use.

// хук, отслеживающий размер окна
function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);
  useEffect(() => {
    const onResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
  }, []);
  return width;
}

18. Сборка и деплой

Создание production-сборки (Vite)

npm run build

Команда создаст папку dist с оптимизированными файлами.

Деплой на статический хостинг (Netlify, Vercel, GitHub Pages)

Пример для GitHub Pages:

  1. Установите gh-pages как dev-зависимость.
  2. Добавьте в package.json:
    "homepage": "https://ваш-username.github.io/имя-репозитория",
    "scripts": {
      "predeploy": "npm run build",
      "deploy": "gh-pages -d dist"
    }
    
  3. Запустите npm run deploy.

Заключительные советы

  • Всегда пишите функциональные компоненты и используйте хуки.
  • Соблюдайте правила хуков (устанавливайте линтер eslint-plugin-react-hooks).
  • Используйте имена, начинающиеся с use, для кастомных хуков.
  • Не оптимизируйте преждевременно — сначала работающий код, потом производительность.
  • Следите за актуальной документацией — React быстро развивается.

Данное пособие охватывает 95% тем, необходимых для профессиональной работы с React. Для углублённого изучения рекомендуется обратиться к официальной документации react.dev.