React: Core
SyntheticEvent — это класс или подход?
SyntheticEvent — это конкретный класс в React, не просто название подхода.
Когда происходит событие (например, ввод в input), React не передаёт в обработчик “сырое” браузерное событие — он создаёт объект SyntheticEvent, который:
- Нормализует поведение событий между браузерами (кроссбраузерная совместимость)
- Предоставляет тот же стандартный интерфейс (
target,preventDefault(),stopPropagation()и т.д.) - Даёт доступ к нативному событию через
e.nativeEvent
<input onChange={(e) => {
console.log(e); // SyntheticEvent объект
console.log(e.nativeEvent); // нативное браузерное событие
console.log(e.target.value); // значение поля
}} />Virtual DOM vs Shadow DOM
React использует Virtual DOM — это не браузерная технология, а паттерн, реализованный самим React.
| Virtual DOM | Shadow DOM | |
|---|---|---|
| Что это | JS-объект, “слепок” UI-дерева для вычисления diff’а | Браузерная технология изоляции DOM/CSS |
| Кто использует | React, Vue | Web Components |
| Цель | Оптимизировать обновления реального DOM | Инкапсуляция стилей и структуры компонента |
Diffing vs Reconciliation
Это не одно и то же:
- Diffing — конкретный алгоритм сравнения старого и нового Virtual DOM (O(n) алгоритм)
- Reconciliation — более широкий процесс. Включает в себя diffing + принятие решений + применение минимально необходимых изменений к реальному DOM
Грубо говоря: diffing — часть reconciliation.
Как React отслеживает изменение в <input>
- Пользователь вводит текст → срабатывает нативное событие браузера
- React перехватывает его через единый глобальный обработчик на корневом элементе (event delegation)
- Создаёт
SyntheticEventи вызываетonChange - Внутри
onChangeвызываетсяsetState→ React планирует перерендер - React строит новое виртуальное дерево и запускает reconciliation (внутри которого работает diffing)
- В реальный DOM вносятся только изменённые узлы
const [value, setValue] = useState('');
<input value={value} onChange={(e) => setValue(e.target.value)} />Ванильный JS vs фреймворки
Ванильный JS
Плюсы:
- Нет зависимостей — нет vendor lock-in
- Минимальный размер бандла
- Полный контроль над кодом
- Хорошо для небольших виджетов и лендингов
Минусы:
- Много бойлерплейта для сложных UI
- Ручное управление DOM — легко получить спагетти-код
- Сложно масштабировать в команде
Фреймворки (React, Vue, Angular)
Плюсы:
- Компонентная архитектура
- Реактивное управление состоянием
- SSR, SSG (Next.js, Nuxt)
- Конвенции облегчают работу в команде
Минусы:
- Overhead для простых страниц
- Кривая обучения
- Обновления могут ломать обратную совместимость
Правило: ванильный JS — для маленьких виджетов; фреймворк — для SPA, дашбордов, приложений со сложным состоянием.
Для чего нужны фреймворки
- Декларативный подход — описываешь что отрисовать, а не как
- Компонентная архитектура — переиспользование UI-блоков
- Управление состоянием и реактивными обновлениями DOM
- Роутинг, экосистема инструментов, единые конвенции в команде
Пропс children
children — специальный пропс, который React автоматически передаёт компоненту. Это всё, что находится между открывающим и закрывающим тегом.
const Card = ({ children }) => (
<div className="card">{children}</div>
);
<Card>
<h1>Заголовок</h1>
<p>Текст</p>
</Card>Render Props паттерн
Паттерн, при котором пропс (или children) — это функция, возвращающая JSX. Компонент делегирует решение о рендере вызывающей стороне.
// children как функция
const DataProvider = ({ children }) => {
const data = useFetchData();
return children(data);
};
<DataProvider>
{(data) => <div>{data.title}</div>}
</DataProvider>
// Или через явный проп render
const MouseTracker = ({ render }) => {
const [pos, setPos] = useState({ x: 0, y: 0 });
return (
<div onMouseMove={(e) => setPos({ x: e.clientX, y: e.clientY })}>
{render(pos)}
</div>
);
};
<MouseTracker render={({ x, y }) => <p>X: {x}, Y: {y}</p>} />Когда использовать: когда нужно переиспользовать логику, но передавать контроль над рендером наружу. В современном React чаще заменяется кастомными хуками.
Что может возвращать компонент?
// 1. JSX-элемент (самый частый случай)
return <div>Hello</div>;
// 2. null — ничего не рендерит, но компонент существует
return null;
// 3. Строка или число
return 'Hello world';
return 42;
// 4. Массив элементов (каждый нужен key)
return [<li key="1">A</li>, <li key="2">B</li>];
// 5. Fragment — группирует без лишнего DOM-узла
return (
<>
<h1>Title</h1>
<p>Text</p>
</>
);
// 6. Portal — рендерит в другой DOM-узел (например, для модалок)
return ReactDOM.createPortal(<Modal />, document.body);| Возвращаемое значение | Описание |
|---|---|
| JSX | Обычный рендер |
null | Ничего не рендерит |
string / number | Текстовый узел в DOM |
Array | Несколько элементов (нужен key) |
<>...</> | Fragment — без лишнего DOM-узла |
createPortal | Вывод в произвольный DOM-контейнер |
Как React понимает, что пропсы изменились?
По умолчанию React не сравнивает пропсы — при ре-рендере родителя все дочерние компоненты ре-рендерятся безусловно. Поверхностное сравнение пропсов (===) выполняется только для компонентов, обёрнутых в React.memo (или классовый PureComponent).
// Примитивы — сравниваются по значению ✅
<Button label="Click" />
// Объекты/функции — сравниваются по ссылке ❌
<Button style={{ color: 'red' }} />
// {} !== {} — каждый рендер новая ссылка → React считает изменившимся
// Решение — мемоизировать
const style = useMemo(() => ({ color: 'red' }), []);
const onClick = useCallback(() => doSomething(), []);
React.memoоборачивает компонент и пропускает ре-рендер если пропсы не изменились по===.
Когда происходит рендер?
// 1. Изменился state
const [count, setCount] = useState(0);
setCount(1); // → рендер
// 2. Изменился context
const theme = useContext(ThemeContext);
// 3. Ре-рендер родителя → ре-рендер всех дочерних компонентовВажно: изменение пропсов само по себе не вызывает рендер — его вызывает родитель, который передаёт новые пропсы.
Что происходит при рендере компонента?
1. Вызывается функция компонента
2. React получает новый JSX (Virtual DOM)
3. Сравнивает новый и старый Virtual DOM (diffing / reconciliation)
4. Вычисляет минимальный набор изменений
5. Применяет изменения в реальный DOM (commit phase)
Рендер ≠ обновление DOM. React может вызвать функцию компонента, но если Virtual DOM не изменился — реальный DOM не трогается.
Способы предотвратить лишние ре-рендеры
// React.memo — не ре-рендерит если пропсы не изменились
const Pure = React.memo(({ count }) => <div>{count}</div>);
// useMemo — мемоизирует вычисленное значение
const result = useMemo(() => heavyCalc(data), [data]);
// useCallback — мемоизирует функцию (стабильная ссылка)
const handler = useCallback(() => doSomething(id), [id]);Цепочка зависимостей при использовании в useEffect:
useMemo(apiParams) → стабильная ссылка на объект
↓
useCallback(loadGame) → стабильная ссылка на функцию
↓
useEffect([loadGame]) → не зациклится
Разница между useState и useRef
| Характеристика | useState | useRef |
|---|---|---|
| Триггер ре-рендера | ✅ Да | ❌ Нет |
| Доступ к значению | Через переменную | Через .current |
| Сохранение значения | Между рендерами | Между рендерами |
| Использование | Управление состоянием UI | Хранение мутабельных значений |
| Инициализация | useState(initialValue) | useRef(initialValue) |
const [count, setCount] = useState(0);
// изменение setCount(1) → ре-рендер компонента
const timerRef = useRef(null);
timerRef.current = setTimeout(...);
// изменение .current → НЕ вызывает ре-рендер