Учебное пособие по 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 — стандарт).
Базовые правила
- Возвращайте один корневой элемент (можно обернуть в <div> или React.Fragment <>...</>).
- Закрывайте все теги — <img />, <br />.
- Имена свойств — camelCase: className вместо class, htmlFor вместо for.
- Встраивание выражений — через фигурные скобки {}.
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:
- Установите gh-pages как dev-зависимость.
- Добавьте в package.json:
"homepage": "https://ваш-username.github.io/имя-репозитория", "scripts": { "predeploy": "npm run build", "deploy": "gh-pages -d dist" } - Запустите npm run deploy.
Заключительные советы
- Всегда пишите функциональные компоненты и используйте хуки.
- Соблюдайте правила хуков (устанавливайте линтер eslint-plugin-react-hooks).
- Используйте имена, начинающиеся с use, для кастомных хуков.
- Не оптимизируйте преждевременно — сначала работающий код, потом производительность.
- Следите за актуальной документацией — React быстро развивается.
Данное пособие охватывает 95% тем, необходимых для профессиональной работы с React. Для углублённого изучения рекомендуется обратиться к официальной документации react.dev.