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 DOMShadow DOM
Что этоJS-объект, “слепок” UI-дерева для вычисления diff’аБраузерная технология изоляции DOM/CSS
Кто используетReact, VueWeb Components
ЦельОптимизировать обновления реального DOMИнкапсуляция стилей и структуры компонента

Diffing vs Reconciliation

Это не одно и то же:

  • Diffing — конкретный алгоритм сравнения старого и нового Virtual DOM (O(n) алгоритм)
  • Reconciliation — более широкий процесс. Включает в себя diffing + принятие решений + применение минимально необходимых изменений к реальному DOM

Грубо говоря: diffing — часть reconciliation.


Как React отслеживает изменение в <input>

  1. Пользователь вводит текст → срабатывает нативное событие браузера
  2. React перехватывает его через единый глобальный обработчик на корневом элементе (event delegation)
  3. Создаёт SyntheticEvent и вызывает onChange
  4. Внутри onChange вызывается setState → React планирует перерендер
  5. React строит новое виртуальное дерево и запускает reconciliation (внутри которого работает diffing)
  6. В реальный 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

ХарактеристикаuseStateuseRef
Триггер ре-рендера✅ Да❌ Нет
Доступ к значениюЧерез переменнуюЧерез .current
Сохранение значенияМежду рендерамиМежду рендерами
ИспользованиеУправление состоянием UIХранение мутабельных значений
ИнициализацияuseState(initialValue)useRef(initialValue)
const [count, setCount] = useState(0);
// изменение setCount(1) → ре-рендер компонента
 
const timerRef = useRef(null);
timerRef.current = setTimeout(...);
// изменение .current → НЕ вызывает ре-рендер